]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
btrfs: reject invalid compression level
authorQu Wenruo <wqu@suse.com>
Mon, 25 Aug 2025 10:26:26 +0000 (19:56 +0930)
committerDavid Sterba <dsterba@suse.com>
Thu, 18 Sep 2025 11:18:49 +0000 (13:18 +0200)
Inspired by recent changes to compression level parsing in
6db1df415d73fc ("btrfs: accept and ignore compression level for lzo")
it turns out that we do not do any extra validation for compression
level input string, thus allowing things like "compress=lzo:invalid" to
be accepted without warnings.

Although we accept levels that are beyond the supported algorithm
ranges, accepting completely invalid level specification is not correct.

Fix the too loose checks for compression level, by doing proper error
handling of kstrtoint(), so that we will reject not only too large
values (beyond int range) but also completely wrong levels like
"lzo:invalid".

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/compression.c
fs/btrfs/compression.h
fs/btrfs/super.c

index d09d622016ef54ffcff4613a8edd62492c0cd2c6..35e3071cec0636f6c0b1ebc47295e997908da73d 100644 (file)
@@ -1616,25 +1616,29 @@ out:
 }
 
 /*
- * Convert the compression suffix (eg. after "zlib" starting with ":") to
- * level, unrecognized string will set the default level. Negative level
- * numbers are allowed.
+ * Convert the compression suffix (eg. after "zlib" starting with ":") to level.
+ *
+ * If the resulting level exceeds the algo's supported levels, it will be clamped.
+ *
+ * Return <0 if no valid string can be found.
+ * Return 0 if everything is fine.
  */
-int btrfs_compress_str2level(unsigned int type, const char *str)
+int btrfs_compress_str2level(unsigned int type, const char *str, int *level_ret)
 {
        int level = 0;
        int ret;
 
-       if (!type)
+       if (!type) {
+               *level_ret = btrfs_compress_set_level(type, level);
                return 0;
+       }
 
        if (str[0] == ':') {
                ret = kstrtoint(str + 1, 10, &level);
                if (ret)
-                       level = 0;
+                       return ret;
        }
 
-       level = btrfs_compress_set_level(type, level);
-
-       return level;
+       *level_ret = btrfs_compress_set_level(type, level);
+       return 0;
 }
index 1b38e707bbd985da5c50fcab2855814c10bec37c..7b41b2b5ff448847a5cecb5947d39af62c03b498 100644 (file)
@@ -102,7 +102,7 @@ void btrfs_submit_compressed_write(struct btrfs_ordered_extent *ordered,
                                   bool writeback);
 void btrfs_submit_compressed_read(struct btrfs_bio *bbio);
 
-int btrfs_compress_str2level(unsigned int type, const char *str);
+int btrfs_compress_str2level(unsigned int type, const char *str, int *level_ret);
 
 struct folio *btrfs_alloc_compr_folio(void);
 void btrfs_free_compr_folio(struct folio *folio);
index e708faf1892f47577972ac2cfd391782bba404d9..a3f9f9e1c02ab557cbe4a6d7e95ce9a97524796d 100644 (file)
@@ -276,6 +276,7 @@ static int btrfs_parse_compress(struct btrfs_fs_context *ctx,
                                const struct fs_parameter *param, int opt)
 {
        const char *string = param->string;
+       int ret;
 
        /*
         * Provide the same semantics as older kernels that don't use fs
@@ -294,15 +295,19 @@ static int btrfs_parse_compress(struct btrfs_fs_context *ctx,
                btrfs_clear_opt(ctx->mount_opt, NODATASUM);
        } else if (btrfs_match_compress_type(string, "zlib", true)) {
                ctx->compress_type = BTRFS_COMPRESS_ZLIB;
-               ctx->compress_level = btrfs_compress_str2level(BTRFS_COMPRESS_ZLIB,
-                                                              string + 4);
+               ret = btrfs_compress_str2level(BTRFS_COMPRESS_ZLIB, string + 4,
+                                              &ctx->compress_level);
+               if (ret < 0)
+                       goto error;
                btrfs_set_opt(ctx->mount_opt, COMPRESS);
                btrfs_clear_opt(ctx->mount_opt, NODATACOW);
                btrfs_clear_opt(ctx->mount_opt, NODATASUM);
        } else if (btrfs_match_compress_type(string, "lzo", true)) {
                ctx->compress_type = BTRFS_COMPRESS_LZO;
-               ctx->compress_level = btrfs_compress_str2level(BTRFS_COMPRESS_LZO,
-                                                              string + 3);
+               ret = btrfs_compress_str2level(BTRFS_COMPRESS_LZO, string + 3,
+                                              &ctx->compress_level);
+               if (ret < 0)
+                       goto error;
                if (string[3] == ':' && string[4])
                        btrfs_warn(NULL, "Compression level ignored for LZO");
                btrfs_set_opt(ctx->mount_opt, COMPRESS);
@@ -310,8 +315,10 @@ static int btrfs_parse_compress(struct btrfs_fs_context *ctx,
                btrfs_clear_opt(ctx->mount_opt, NODATASUM);
        } else if (btrfs_match_compress_type(string, "zstd", true)) {
                ctx->compress_type = BTRFS_COMPRESS_ZSTD;
-               ctx->compress_level = btrfs_compress_str2level(BTRFS_COMPRESS_ZSTD,
-                                                              string + 4);
+               ret = btrfs_compress_str2level(BTRFS_COMPRESS_ZSTD, string + 4,
+                                              &ctx->compress_level);
+               if (ret < 0)
+                       goto error;
                btrfs_set_opt(ctx->mount_opt, COMPRESS);
                btrfs_clear_opt(ctx->mount_opt, NODATACOW);
                btrfs_clear_opt(ctx->mount_opt, NODATASUM);
@@ -322,10 +329,14 @@ static int btrfs_parse_compress(struct btrfs_fs_context *ctx,
                btrfs_clear_opt(ctx->mount_opt, COMPRESS);
                btrfs_clear_opt(ctx->mount_opt, FORCE_COMPRESS);
        } else {
-               btrfs_err(NULL, "unrecognized compression value %s", string);
-               return -EINVAL;
+               ret = -EINVAL;
+               goto error;
        }
        return 0;
+error:
+       btrfs_err(NULL, "failed to parse compression option '%s'", string);
+       return ret;
+
 }
 
 static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param)