From ea02727f2f7d0c497cc0c7350d983ec7e0a74a7c Mon Sep 17 00:00:00 2001 From: Zygo Blaxell Date: Sun, 19 Jul 2026 14:33:53 -0400 Subject: [PATCH] btrfs: props: validate compression property values strictly The btrfs.compression property validator matches only the algorithm name prefix, so values like "zstdgarbage" or "zstd:banana" are accepted and stored verbatim. Now that ":level" suffixes are meaningful, validate new values strictly: accept exactly an algorithm name, optionally followed by a ":level" suffix that btrfs_compress_str2level() can parse, mirroring the mount option validation from commit b98b20830057 ("btrfs: reject invalid compression level"). Out of range levels are clamped, also matching the mount options. "no" and "none" are accepted as before. Embedded NUL bytes are rejected before parsing the length-delimited xattr value: otherwise the temporary NUL-terminated suffix buffer would let a value such as "zstd:3" followed by a NUL and junk pass validation and be stored verbatim. Values stored by old kernels are not affected: property loading goes through the apply hook, which remains permissive, so existing inodes with sloppy stored values keep working; only new setxattr calls see the stricter checks. This is a user-visible behavior change: applications that set malformed property values, which were previously accepted and ignored, will now receive EINVAL. It is split into its own commit so that it can be accepted or rejected independently of per-inode compression level support. Signed-off-by: Zygo Blaxell Assisted-by: Claude:claude-fable-5 Assisted-by: Codex:gpt-5 --- fs/btrfs/props.c | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/fs/btrfs/props.c b/fs/btrfs/props.c index d78577bd4a00c..76ff8b0ce2dd6 100644 --- a/fs/btrfs/props.c +++ b/fs/btrfs/props.c @@ -295,19 +295,51 @@ int btrfs_load_inode_props(struct btrfs_inode *inode, struct btrfs_path *path) static int prop_compression_validate(const struct btrfs_inode *inode, const char *value, size_t len) { + size_t type_len; + int type; + if (!value) return 0; + if (memchr(value, '\0', len)) + return -EINVAL; - if (btrfs_compress_is_valid_type(value, len)) { - if (!btrfs_inode_can_compress(inode)) - return -EINVAL; + if ((len == 2 && strncmp("no", value, 2) == 0) || + (len == 4 && strncmp("none", value, 4) == 0)) return 0; + + if (len >= 3 && strncmp("lzo", value, 3) == 0) { + type = BTRFS_COMPRESS_LZO; + type_len = 3; + } else if (len >= 4 && strncmp("zlib", value, 4) == 0) { + type = BTRFS_COMPRESS_ZLIB; + type_len = 4; + } else if (len >= 4 && strncmp("zstd", value, 4) == 0) { + type = BTRFS_COMPRESS_ZSTD; + type_len = 4; + } else { + return -EINVAL; } - if ((len == 2 && strncmp("no", value, 2) == 0) || - (len == 4 && strncmp("none", value, 4) == 0)) + if (!btrfs_inode_can_compress(inode)) + return -EINVAL; + + if (len == type_len) return 0; + /* Only a valid ":level" suffix may follow the algorithm name. */ + if (value[type_len] == ':') { + char str[16]; + size_t suffix_len = len - type_len; + int level; + + if (suffix_len >= sizeof(str)) + return -EINVAL; + memcpy(str, value + type_len, suffix_len); + str[suffix_len] = 0; + if (btrfs_compress_str2level(type, str, &level) == 0) + return 0; + } + return -EINVAL; } -- 2.53.0