btrfs: reject invalid compression level

Inspired by recent changes to compression level parsing in
6db1df415d ("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>
This commit is contained in:
Qu Wenruo 2025-08-25 19:56:26 +09:30 committed by David Sterba
parent ed4e6b5d64
commit b98b208300
3 changed files with 33 additions and 18 deletions

View 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;
}

View 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);

View 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)