]> git.hungrycats.org Git - linux/commitdiff
btrfs: props: validate compression property values strictly misc-next/topics/compress-fixes
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sun, 19 Jul 2026 18:33:53 +0000 (14:33 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Fri, 18 Sep 2026 21:36:15 +0000 (17:36 -0400)
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 <ce3g8jdj@umail.furryterror.org>
Assisted-by: Claude:claude-fable-5
Assisted-by: Codex:gpt-5
fs/btrfs/props.c

index d78577bd4a00c7bdf94200ed05fa41206d1173a3..76ff8b0ce2dd646cd5f190b0ca9c895b3d0c38cb 100644 (file)
@@ -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;
 }