From decc9abb64ff2250c3019fe4478f195432032d12 Mon Sep 17 00:00:00 2001 From: Haoqin Huang Date: Sat, 27 Jun 2026 15:02:14 +0800 Subject: [PATCH 1/3] zram: fix zstd dict use-after-free on per-CPU error path zstd_setup_params() creates global cdict and ddict stored in params->drv_data, shared across all per-CPU contexts. When a per-CPU zstd_create() failed, its error path called zstd_release_params() which freed those shared objects while other per-CPU contexts might already hold references to them. Remove the premature zstd_release_params() from the per-CPU error path, the global cdict/ddict are properly released later by zstd_release_params(), called from zcomp_init()'s cleanup or from zcomp_destroy(). Fixes: 6a559ecd6e7e ("zram: add dictionary support to zstd backend") Signed-off-by: Haoqin Huang Reviewed-by: Rongwei Wang --- drivers/block/zram/backend_zstd.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/block/zram/backend_zstd.c b/drivers/block/zram/backend_zstd.c index d00b548056dc2..2584f47c9b3c5 100644 --- a/drivers/block/zram/backend_zstd.c +++ b/drivers/block/zram/backend_zstd.c @@ -161,7 +161,6 @@ static int zstd_create(struct zcomp_params *params, struct zcomp_ctx *ctx) return 0; error: - zstd_release_params(params); zstd_destroy(ctx); return -EINVAL; } From eacee497bda83a65f51fc993b8b71d22214cac05 Mon Sep 17 00:00:00 2001 From: Haoqin Huang Date: Sat, 27 Jun 2026 15:02:15 +0800 Subject: [PATCH 2/3] zram: add per-backend capability flags and validate parameters early Writing dict or level parameters for algorithms that don't support them was silently accepted but had no effect. Out-of-range levels were silently clamped by the underlying library. Dict read failures always lost the real error from kernel_read_file_from_path(). Add caps, level_min and level_max to zcomp_ops and validate user-supplied parameters in algorithm_params_store() before storing, giving immediate error feedback. Also fix comp_params_store() to read the new dict into a temporary buffer before resetting old parameters, making the update atomic. Signed-off-by: Haoqin Huang Reviewed-by: Rongwei Wang --- drivers/block/zram/backend_842.c | 1 + drivers/block/zram/backend_deflate.c | 3 +++ drivers/block/zram/backend_lz4.c | 3 +++ drivers/block/zram/backend_lz4hc.c | 3 +++ drivers/block/zram/backend_lzo.c | 1 + drivers/block/zram/backend_lzorle.c | 1 + drivers/block/zram/backend_zstd.c | 3 +++ drivers/block/zram/zcomp.c | 23 ++++++++++++++++++++ drivers/block/zram/zcomp.h | 8 +++++++ drivers/block/zram/zram_drv.c | 32 +++++++++++++++++++++------- include/linux/zstd_lib.h | 1 + lib/zstd/compress/clevels.h | 1 - 12 files changed, 71 insertions(+), 9 deletions(-) diff --git a/drivers/block/zram/backend_842.c b/drivers/block/zram/backend_842.c index 10d9d5c60f531..d796ebda1fa0d 100644 --- a/drivers/block/zram/backend_842.c +++ b/drivers/block/zram/backend_842.c @@ -57,5 +57,6 @@ const struct zcomp_ops backend_842 = { .destroy_ctx = destroy_842, .setup_params = setup_params_842, .release_params = release_params_842, + .caps = 0, .name = "842", }; diff --git a/drivers/block/zram/backend_deflate.c b/drivers/block/zram/backend_deflate.c index f92a52a720d1e..cedc3daad33af 100644 --- a/drivers/block/zram/backend_deflate.c +++ b/drivers/block/zram/backend_deflate.c @@ -144,5 +144,8 @@ const struct zcomp_ops backend_deflate = { .destroy_ctx = deflate_destroy, .setup_params = deflate_setup_params, .release_params = deflate_release_params, + .caps = ZCOMP_CAP_LEVEL, + .level_min = Z_DEFAULT_COMPRESSION, + .level_max = Z_BEST_COMPRESSION, .name = "deflate", }; diff --git a/drivers/block/zram/backend_lz4.c b/drivers/block/zram/backend_lz4.c index c449d511ba86f..bd1e5ca4d1342 100644 --- a/drivers/block/zram/backend_lz4.c +++ b/drivers/block/zram/backend_lz4.c @@ -146,5 +146,8 @@ const struct zcomp_ops backend_lz4 = { .destroy_ctx = lz4_destroy, .setup_params = lz4_setup_params, .release_params = lz4_release_params, + .caps = ZCOMP_CAP_DICT | ZCOMP_CAP_LEVEL, + .level_min = LZ4_ACCELERATION_DEFAULT, + .level_max = 65535, .name = "lz4", }; diff --git a/drivers/block/zram/backend_lz4hc.c b/drivers/block/zram/backend_lz4hc.c index f6a336acfe202..0e0d7c68a7d42 100644 --- a/drivers/block/zram/backend_lz4hc.c +++ b/drivers/block/zram/backend_lz4hc.c @@ -124,5 +124,8 @@ const struct zcomp_ops backend_lz4hc = { .destroy_ctx = lz4hc_destroy, .setup_params = lz4hc_setup_params, .release_params = lz4hc_release_params, + .caps = ZCOMP_CAP_DICT | ZCOMP_CAP_LEVEL, + .level_min = LZ4HC_MIN_CLEVEL, + .level_max = LZ4HC_MAX_CLEVEL, .name = "lz4hc", }; diff --git a/drivers/block/zram/backend_lzo.c b/drivers/block/zram/backend_lzo.c index 4c906beaae6b3..965f007e2ca89 100644 --- a/drivers/block/zram/backend_lzo.c +++ b/drivers/block/zram/backend_lzo.c @@ -55,5 +55,6 @@ const struct zcomp_ops backend_lzo = { .destroy_ctx = lzo_destroy, .setup_params = lzo_setup_params, .release_params = lzo_release_params, + .caps = 0, .name = "lzo", }; diff --git a/drivers/block/zram/backend_lzorle.c b/drivers/block/zram/backend_lzorle.c index 10640c96cbfca..757b4598be030 100644 --- a/drivers/block/zram/backend_lzorle.c +++ b/drivers/block/zram/backend_lzorle.c @@ -55,5 +55,6 @@ const struct zcomp_ops backend_lzorle = { .destroy_ctx = lzorle_destroy, .setup_params = lzorle_setup_params, .release_params = lzorle_release_params, + .caps = 0, .name = "lzo-rle", }; diff --git a/drivers/block/zram/backend_zstd.c b/drivers/block/zram/backend_zstd.c index 2584f47c9b3c5..0fbd2460883a8 100644 --- a/drivers/block/zram/backend_zstd.c +++ b/drivers/block/zram/backend_zstd.c @@ -212,5 +212,8 @@ const struct zcomp_ops backend_zstd = { .destroy_ctx = zstd_destroy, .setup_params = zstd_setup_params, .release_params = zstd_release_params, + .caps = ZCOMP_CAP_DICT | ZCOMP_CAP_LEVEL, + .level_min = (int)-ZSTD_TARGETLENGTH_MAX, + .level_max = ZSTD_MAX_CLEVEL, .name = "zstd", }; diff --git a/drivers/block/zram/zcomp.c b/drivers/block/zram/zcomp.c index 974c4691887e6..15de28b50d427 100644 --- a/drivers/block/zram/zcomp.c +++ b/drivers/block/zram/zcomp.c @@ -9,6 +9,9 @@ #include #include #include +#include +#include +#include #include "zcomp.h" @@ -94,6 +97,26 @@ const char *zcomp_lookup_backend_name(const char *comp) return NULL; } +unsigned int zcomp_get_caps(const char *comp) +{ + const struct zcomp_ops *backend = lookup_backend_ops(comp); + + return backend ? backend->caps : 0; +} + +int zcomp_validate_level(const char *comp, s32 level) +{ + const struct zcomp_ops *backend = lookup_backend_ops(comp); + + if (!backend) + return -EINVAL; + if (!(backend->caps & ZCOMP_CAP_LEVEL)) + return -EOPNOTSUPP; + if (level < backend->level_min || level > backend->level_max) + return -EINVAL; + return 0; +} + /* show available compressors */ ssize_t zcomp_available_show(const char *comp, char *buf, ssize_t at) { diff --git a/drivers/block/zram/zcomp.h b/drivers/block/zram/zcomp.h index 81a0f3f6ff487..16f812a94c67d 100644 --- a/drivers/block/zram/zcomp.h +++ b/drivers/block/zram/zcomp.h @@ -7,6 +7,9 @@ #define ZCOMP_PARAM_NOT_SET INT_MIN +#define ZCOMP_CAP_DICT BIT(0) /* dictionary support */ +#define ZCOMP_CAP_LEVEL BIT(1) /* adjustable compression level */ + struct deflate_params { s32 winbits; }; @@ -66,6 +69,9 @@ struct zcomp_ops { int (*setup_params)(struct zcomp_params *params); void (*release_params)(struct zcomp_params *params); + unsigned int caps; + s32 level_min; + s32 level_max; const char *name; }; @@ -81,6 +87,8 @@ int zcomp_cpu_up_prepare(unsigned int cpu, struct hlist_node *node); int zcomp_cpu_dead(unsigned int cpu, struct hlist_node *node); ssize_t zcomp_available_show(const char *comp, char *buf, ssize_t at); const char *zcomp_lookup_backend_name(const char *comp); +unsigned int zcomp_get_caps(const char *comp); +int zcomp_validate_level(const char *comp, s32 level); struct zcomp *zcomp_create(const char *alg, struct zcomp_params *params); void zcomp_destroy(struct zcomp *comp); diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c index ace65c586072b..a667d0672720a 100644 --- a/drivers/block/zram/zram_drv.c +++ b/drivers/block/zram/zram_drv.c @@ -1699,21 +1699,24 @@ static int comp_params_store(struct zram *zram, u32 prio, s32 level, const char *dict_path, struct deflate_params *deflate_params) { + void *new_dict = NULL; ssize_t sz = 0; - comp_params_reset(zram, prio); - if (dict_path) { - sz = kernel_read_file_from_path(dict_path, 0, - &zram->params[prio].dict, - INT_MAX, - NULL, - READING_POLICY); - if (sz < 0) + sz = kernel_read_file_from_path(dict_path, 0, &new_dict, + INT_MAX, NULL, READING_POLICY); + if (sz < 0) { + vfree(new_dict); + return sz; + } else if (sz == 0) { + vfree(new_dict); return -EINVAL; + } } + comp_params_reset(zram, prio); zram->params[prio].dict_sz = sz; + zram->params[prio].dict = new_dict; zram->params[prio].level = level; zram->params[prio].deflate.winbits = deflate_params->winbits; return 0; @@ -1794,6 +1797,19 @@ static ssize_t algorithm_params_store(struct device *dev, return -EINVAL; } + if (zram->comp_algs[prio]) { + unsigned int caps = zcomp_get_caps(zram->comp_algs[prio]); + + if (dict_path && !(caps & ZCOMP_CAP_DICT)) + return -EOPNOTSUPP; + + if (level != ZCOMP_PARAM_NOT_SET) { + ret = zcomp_validate_level(zram->comp_algs[prio], level); + if (ret) + return ret; + } + } + ret = comp_params_store(zram, prio, level, dict_path, &deflate_params); return ret ? ret : len; } diff --git a/include/linux/zstd_lib.h b/include/linux/zstd_lib.h index e295d4125dde8..f4b26844e53ad 100644 --- a/include/linux/zstd_lib.h +++ b/include/linux/zstd_lib.h @@ -1241,6 +1241,7 @@ ZSTDLIB_API size_t ZSTD_sizeof_DDict(const ZSTD_DDict* ddict); #define ZSTD_SEARCHLOG_MIN 1 #define ZSTD_MINMATCH_MAX 7 /* only for ZSTD_fast, other strategies are limited to 6 */ #define ZSTD_MINMATCH_MIN 3 /* only for ZSTD_btopt+, faster strategies are limited to 4 */ +#define ZSTD_MAX_CLEVEL 22 #define ZSTD_TARGETLENGTH_MAX ZSTD_BLOCKSIZE_MAX #define ZSTD_TARGETLENGTH_MIN 0 /* note : comparing this constant to an unsigned results in a tautological test */ #define ZSTD_STRATEGY_MIN ZSTD_fast diff --git a/lib/zstd/compress/clevels.h b/lib/zstd/compress/clevels.h index 6ab8be6532efc..db3b275b502e6 100644 --- a/lib/zstd/compress/clevels.h +++ b/lib/zstd/compress/clevels.h @@ -17,7 +17,6 @@ /*-===== Pre-defined compression levels =====-*/ -#define ZSTD_MAX_CLEVEL 22 __attribute__((__unused__)) From 30582ee0b14abb0cb4b8731e9ddf9e74d649be41 Mon Sep 17 00:00:00 2001 From: Haoqin Huang Date: Sat, 27 Jun 2026 15:02:16 +0800 Subject: [PATCH 3/3] zram: reset per-priority params when changing algorithm before init Parameters validated against one algorithm may be invalid for another (e.g. lz4 accepts level=65535 but zstd does not). Although algorithm changes are blocked after disksize is set, they are allowed before device initialization. Reset per-priority params on algorithm change so that stale parameters do not silently carry over. Signed-off-by: Haoqin Huang Reviewed-by: Rongwei Wang --- drivers/block/zram/zram_drv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/block/zram/zram_drv.c b/drivers/block/zram/zram_drv.c index a667d0672720a..6b47586e8718c 100644 --- a/drivers/block/zram/zram_drv.c +++ b/drivers/block/zram/zram_drv.c @@ -57,6 +57,7 @@ static size_t huge_class_size; static const struct block_device_operations zram_devops; static void slot_free(struct zram *zram, u32 index); +static void comp_params_reset(struct zram *zram, u32 prio); #define slot_dep_map(zram, index) (&(zram)->table[(index)].dep_map) static void slot_lock_init(struct zram *zram, u32 index) @@ -1681,6 +1682,7 @@ static int __comp_algorithm_store(struct zram *zram, u32 prio, const char *buf) } comp_algorithm_set(zram, prio, alg); + comp_params_reset(zram, prio); return 0; }