Commit b98b2083 authored by Qu Wenruo's avatar Qu Wenruo Committed by David Sterba
Browse files

btrfs: reject invalid compression level



Inspired by recent changes to compression level parsing in
6db1df41 ("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: default avatarQu Wenruo <wqu@suse.com>
Reviewed-by: default avatarDavid Sterba <dsterba@suse.com>
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
parent ed4e6b5d
Loading
Loading
Loading
Loading
+13 −9
Original line number Diff line number Diff line
@@ -1616,25 +1616,29 @@ int btrfs_compress_heuristic(struct btrfs_inode *inode, u64 start, u64 end)
}

/*
 * 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;
}
+1 −1
Original line number Diff line number Diff line
@@ -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);
+19 −8
Original line number Diff line number Diff line
@@ -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)