Merge tag 'for-7.2-rc3-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux

Pull btrfs fixes from David Sterba:

 - fix root structure leak after relocation error

 - fix optimization when checksums are read from commit root, fall back
   to checksum root during relocation

 - in tree-checker, validate length of inode reference in items

 - validate properties before setting them

 - validate free space cache entries on load

 - transaction abort fixes

 - fix printing of internal trees as signed numbers

 - add error messages after critical lzo compression errors

* tag 'for-7.2-rc3-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux:
  btrfs: print-tree: print header owner as signed
  btrfs: decentralize transaction aborts in create_reloc_root()
  btrfs: tree-checker: validate INODE_REF's namelen
  btrfs: lzo: add error message for invalid headers
  btrfs: fallback to transaction csum tree on a commit root csum miss
  btrfs: fix root leak if its reloc root is unexpected in merge_reloc_roots()
  btrfs: reject free space cache with more entries than pages
  btrfs: fix transaction abort logic in btrfs_fileattr_set()
  btrfs: validate properties before setting them
This commit is contained in:
Linus Torvalds
2026-07-14 09:10:27 -07:00
8 changed files with 82 additions and 25 deletions

View File

@@ -358,6 +358,7 @@ int btrfs_lookup_bio_sums(struct btrfs_bio *bbio)
const unsigned int nblocks = orig_len >> fs_info->sectorsize_bits;
int ret = 0;
u32 bio_offset = 0;
bool using_commit_root = false;
if ((inode->flags & BTRFS_INODE_NODATASUM) ||
test_bit(BTRFS_FS_STATE_NO_DATA_CSUMS, &fs_info->fs_state))
@@ -431,6 +432,7 @@ int btrfs_lookup_bio_sums(struct btrfs_bio *bbio)
* from across transactions.
*/
if (bbio->csum_search_commit_root) {
using_commit_root = true;
path->search_commit_root = true;
path->skip_locking = true;
down_read(&fs_info->commit_root_sem);
@@ -463,6 +465,28 @@ int btrfs_lookup_bio_sums(struct btrfs_bio *bbio)
* assume this is the case.
*/
if (count == 0) {
/*
* If an extent is relocated in the current transaction
* then relocation writes a new csum without updating
* the extent map generation. Until the next commit, we
* will see a hole in that case, so we need to fallback
* to searching the transaction csum root.
*
* Note that a commit root lookup of a referenced extent can
* only miss, not return a stale csum. A freed extent's csum
* is deleted in the same transaction and its bytenr is not
* reusable until that transaction has committed and the
* extent is unpinned.
*/
if (using_commit_root) {
up_read(&fs_info->commit_root_sem);
using_commit_root = false;
path->search_commit_root = false;
path->skip_locking = false;
btrfs_release_path(path);
continue;
}
memset(csum_dst, 0, csum_size);
count = 1;
@@ -481,7 +505,7 @@ int btrfs_lookup_bio_sums(struct btrfs_bio *bbio)
bio_offset += count * sectorsize;
}
if (bbio->csum_search_commit_root)
if (using_commit_root)
up_read(&fs_info->commit_root_sem);
return ret;
}

View File

@@ -551,6 +551,9 @@ static int io_ctl_check_crc(struct btrfs_io_ctl *io_ctl, int index)
u32 crc = ~(u32)0;
unsigned offset = 0;
if (index >= io_ctl->num_pages)
return -EIO;
if (index == 0)
offset = sizeof(u32) * io_ctl->num_pages;

View File

@@ -289,6 +289,7 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap,
int ret;
const char *comp = NULL;
u32 inode_flags;
bool prop_set = false;
if (btrfs_root_readonly(root))
return -EROFS;
@@ -401,16 +402,15 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap,
if (comp) {
ret = btrfs_set_prop(trans, inode, "btrfs.compression",
comp, strlen(comp), 0);
if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
if (ret)
goto out_end_trans;
}
prop_set = true;
} else {
ret = btrfs_set_prop(trans, inode, "btrfs.compression", NULL, 0, 0);
if (unlikely(ret && ret != -ENODATA)) {
btrfs_abort_transaction(trans, ret);
prop_set = (ret == 0);
/* If ret == -ENODATA ignore and proceed to update inode item. */
if (ret && ret != -ENODATA)
goto out_end_trans;
}
}
update_flags:
@@ -420,6 +420,12 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap,
inode_inc_iversion(&inode->vfs_inode);
inode_set_ctime_current(&inode->vfs_inode);
ret = btrfs_update_inode(trans, inode);
/*
* If we set a property or deleted one, we must abort if we fail to
* update the inode, to avoid persisting an inconsistent state.
*/
if (unlikely(ret && prop_set))
btrfs_abort_transaction(trans, ret);
out_end_trans:
btrfs_end_transaction(trans);

View File

@@ -552,17 +552,26 @@ int lzo_decompress(struct list_head *ws, const u8 *data_in,
size_t max_segment_len = workspace_buf_length(fs_info);
int ret;
if (unlikely(srclen < LZO_LEN || srclen > max_segment_len + LZO_LEN * 2))
if (unlikely(srclen < LZO_LEN || srclen > max_segment_len + LZO_LEN * 2)) {
btrfs_err(fs_info, "invalid lzo header length, has %zu expect (%u, %zu)",
srclen, LZO_LEN, max_segment_len + LZO_LEN * 2);
return -EUCLEAN;
}
in_len = get_unaligned_le32(data_in);
if (unlikely(in_len != srclen))
if (unlikely(in_len != srclen)) {
btrfs_err(fs_info, "invalid lzo header length, has %zu expect %zu",
in_len, srclen);
return -EUCLEAN;
}
data_in += LZO_LEN;
in_len = get_unaligned_le32(data_in);
if (unlikely(in_len != srclen - LZO_LEN * 2))
if (unlikely(in_len != srclen - LZO_LEN * 2)) {
btrfs_err(fs_info, "invalid lzo segment length, has %zu expect %zu",
in_len, srclen - LZO_LEN * 2);
return -EUCLEAN;
}
data_in += LZO_LEN;
out_len = sectorsize;

View File

@@ -449,9 +449,9 @@ void btrfs_print_leaf(const struct extent_buffer *l)
nr = btrfs_header_nritems(l);
btrfs_info(fs_info,
"leaf %llu gen %llu total ptrs %d free space %d owner %llu",
"leaf %llu gen %llu total ptrs %d free space %d owner %lld",
btrfs_header_bytenr(l), btrfs_header_generation(l), nr,
btrfs_leaf_free_space(l), btrfs_header_owner(l));
btrfs_leaf_free_space(l), (s64)btrfs_header_owner(l));
print_eb_refs_lock(l);
for (i = 0 ; i < nr ; i++) {
char key_buf[KEY_TYPE_BUF_SIZE];
@@ -600,10 +600,10 @@ void btrfs_print_tree(const struct extent_buffer *c, bool follow)
return;
}
btrfs_info(fs_info,
"node %llu level %d gen %llu total ptrs %d free spc %u owner %llu",
"node %llu level %d gen %llu total ptrs %d free spc %u owner %lld",
btrfs_header_bytenr(c), level, btrfs_header_generation(c),
nr, (u32)BTRFS_NODEPTRS_PER_BLOCK(fs_info) - nr,
btrfs_header_owner(c));
(s64)btrfs_header_owner(c));
print_eb_refs_lock(c);
for (i = 0; i < nr; i++) {
btrfs_node_key_to_cpu(c, &key, i);

View File

@@ -127,14 +127,24 @@ int btrfs_set_prop(struct btrfs_trans_handle *trans, struct btrfs_inode *inode,
return ret;
}
ret = handler->validate(inode, value, value_len);
if (ret)
return ret;
ret = btrfs_setxattr(trans, &inode->vfs_inode, handler->xattr_name, value,
value_len, flags);
if (ret)
return ret;
ret = handler->apply(inode, value, value_len);
if (ret) {
btrfs_setxattr(trans, &inode->vfs_inode, handler->xattr_name, NULL,
0, flags);
/* We validated before, so it should not fail here. */
ASSERT(ret == 0);
if (unlikely(ret)) {
int ret2;
/* Try to delete xattr, if not possible abort transaction. */
ret2 = btrfs_setxattr(trans, &inode->vfs_inode, handler->xattr_name,
NULL, 0, flags);
if (unlikely(ret2))
btrfs_abort_transaction(trans, ret2);
return ret;
}

View File

@@ -719,21 +719,19 @@ static struct btrfs_root *create_reloc_root(struct btrfs_trans_handle *trans,
ret = btrfs_insert_root(trans, fs_info->tree_root,
&root_key, root_item);
if (ret)
goto abort;
if (unlikely(ret)) {
btrfs_abort_transaction(trans, ret);
return ERR_PTR(ret);
}
reloc_root = btrfs_read_tree_root(fs_info->tree_root, &root_key);
if (IS_ERR(reloc_root)) {
ret = PTR_ERR(reloc_root);
goto abort;
btrfs_abort_transaction(trans, PTR_ERR(reloc_root));
return ERR_CAST(reloc_root);
}
set_bit(BTRFS_ROOT_SHAREABLE, &reloc_root->state);
btrfs_set_root_last_trans(reloc_root, trans->transid);
return reloc_root;
abort:
btrfs_abort_transaction(trans, ret);
return ERR_PTR(ret);
}
/*
@@ -1912,6 +1910,7 @@ void merge_reloc_roots(struct reloc_control *rc)
* corruption, e.g. bad reloc tree key offset.
*/
ret = -EINVAL;
btrfs_put_root(root);
goto out;
}
ret = merge_reloc_root(rc, root);

View File

@@ -1923,6 +1923,12 @@ static int check_inode_ref(struct extent_buffer *leaf,
iref = (struct btrfs_inode_ref *)ptr;
namelen = btrfs_inode_ref_name_len(leaf, iref);
if (unlikely(namelen == 0 || namelen > BTRFS_NAME_LEN)) {
inode_ref_err(leaf, slot,
"invalid inode ref name length, has %u expect [1, %u]",
namelen, BTRFS_NAME_LEN);
return -EUCLEAN;
}
if (unlikely(ptr + sizeof(*iref) + namelen > end)) {
inode_ref_err(leaf, slot,
"inode ref overflow, ptr %lu end %lu namelen %u",