linux-stable/fs/btrfs/ref-verify.c

1038 lines
25 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2014 Facebook. All rights reserved.
*/
#include <linux/sched.h>
#include <linux/stacktrace.h>
#include "messages.h"
#include "ctree.h"
#include "disk-io.h"
#include "locking.h"
#include "delayed-ref.h"
#include "ref-verify.h"
#include "fs.h"
#include "accessors.h"
/*
* Used to keep track the roots and number of refs each root has for a given
* bytenr. This just tracks the number of direct references, no shared
* references.
*/
struct root_entry {
u64 root_objectid;
u64 num_refs;
struct rb_node node;
};
/*
* These are meant to represent what should exist in the extent tree, these can
* be used to verify the extent tree is consistent as these should all match
* what the extent tree says.
*/
struct ref_entry {
u64 root_objectid;
u64 parent;
u64 owner;
u64 offset;
u64 num_refs;
struct rb_node node;
};
#define MAX_TRACE 16
/*
* Whenever we add/remove a reference we record the action. The action maps
* back to the delayed ref action. We hold the ref we are changing in the
* action so we can account for the history properly, and we record the root we
* were called with since it could be different from ref_root. We also store
* stack traces because that's how I roll.
*/
struct ref_action {
int action;
u64 root;
struct ref_entry ref;
struct list_head list;
unsigned long trace[MAX_TRACE];
unsigned int trace_len;
};
/*
* One of these for every block we reference, it holds the roots and references
* to it as well as all of the ref actions that have occurred to it. We never
* free it until we unmount the file system in order to make sure re-allocations
* are happening properly.
*/
struct block_entry {
u64 bytenr;
u64 len;
u64 num_refs;
int metadata;
int from_disk;
struct rb_root roots;
struct rb_root refs;
struct rb_node node;
struct list_head actions;
};
static struct block_entry *insert_block_entry(struct rb_root *root,
struct block_entry *be)
{
struct rb_node **p = &root->rb_node;
struct rb_node *parent_node = NULL;
struct block_entry *entry;
while (*p) {
parent_node = *p;
entry = rb_entry(parent_node, struct block_entry, node);
if (entry->bytenr > be->bytenr)
p = &(*p)->rb_left;
else if (entry->bytenr < be->bytenr)
p = &(*p)->rb_right;
else
return entry;
}
rb_link_node(&be->node, parent_node, p);
rb_insert_color(&be->node, root);
return NULL;
}
static struct block_entry *lookup_block_entry(struct rb_root *root, u64 bytenr)
{
struct rb_node *n;
struct block_entry *entry = NULL;
n = root->rb_node;
while (n) {
entry = rb_entry(n, struct block_entry, node);
if (entry->bytenr < bytenr)
n = n->rb_right;
else if (entry->bytenr > bytenr)
n = n->rb_left;
else
return entry;
}
return NULL;
}
static struct root_entry *insert_root_entry(struct rb_root *root,
struct root_entry *re)
{
struct rb_node **p = &root->rb_node;
struct rb_node *parent_node = NULL;
struct root_entry *entry;
while (*p) {
parent_node = *p;
entry = rb_entry(parent_node, struct root_entry, node);
if (entry->root_objectid > re->root_objectid)
p = &(*p)->rb_left;
else if (entry->root_objectid < re->root_objectid)
p = &(*p)->rb_right;
else
return entry;
}
rb_link_node(&re->node, parent_node, p);
rb_insert_color(&re->node, root);
return NULL;
}
static int comp_refs(struct ref_entry *ref1, struct ref_entry *ref2)
{
if (ref1->root_objectid < ref2->root_objectid)
return -1;
if (ref1->root_objectid > ref2->root_objectid)
return 1;
if (ref1->parent < ref2->parent)
return -1;
if (ref1->parent > ref2->parent)
return 1;
if (ref1->owner < ref2->owner)
return -1;
if (ref1->owner > ref2->owner)
return 1;
if (ref1->offset < ref2->offset)
return -1;
if (ref1->offset > ref2->offset)
return 1;
return 0;
}
static struct ref_entry *insert_ref_entry(struct rb_root *root,
struct ref_entry *ref)
{
struct rb_node **p = &root->rb_node;
struct rb_node *parent_node = NULL;
struct ref_entry *entry;
int cmp;
while (*p) {
parent_node = *p;
entry = rb_entry(parent_node, struct ref_entry, node);
cmp = comp_refs(entry, ref);
if (cmp > 0)
p = &(*p)->rb_left;
else if (cmp < 0)
p = &(*p)->rb_right;
else
return entry;
}
rb_link_node(&ref->node, parent_node, p);
rb_insert_color(&ref->node, root);
return NULL;
}
static struct root_entry *lookup_root_entry(struct rb_root *root, u64 objectid)
{
struct rb_node *n;
struct root_entry *entry = NULL;
n = root->rb_node;
while (n) {
entry = rb_entry(n, struct root_entry, node);
if (entry->root_objectid < objectid)
n = n->rb_right;
else if (entry->root_objectid > objectid)
n = n->rb_left;
else
return entry;
}
return NULL;
}
#ifdef CONFIG_STACKTRACE
static void __save_stack_trace(struct ref_action *ra)
{
btrfs: ref-verify: Simplify stack trace retrieval Replace the indirection through struct stack_trace with an invocation of the storage array based interface. Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Johannes Thumshirn <jthumshirn@suse.de> Reviewed-by: Josh Poimboeuf <jpoimboe@redhat.com> Acked-by: David Sterba <dsterba@suse.com> Cc: Andy Lutomirski <luto@kernel.org> Cc: Chris Mason <clm@fb.com> Cc: Josef Bacik <josef@toxicpanda.com> Cc: linux-btrfs@vger.kernel.org Cc: Steven Rostedt <rostedt@goodmis.org> Cc: Alexander Potapenko <glider@google.com> Cc: Alexey Dobriyan <adobriyan@gmail.com> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Christoph Lameter <cl@linux.com> Cc: Pekka Enberg <penberg@kernel.org> Cc: linux-mm@kvack.org Cc: David Rientjes <rientjes@google.com> Cc: Catalin Marinas <catalin.marinas@arm.com> Cc: Dmitry Vyukov <dvyukov@google.com> Cc: Andrey Ryabinin <aryabinin@virtuozzo.com> Cc: kasan-dev@googlegroups.com Cc: Mike Rapoport <rppt@linux.vnet.ibm.com> Cc: Akinobu Mita <akinobu.mita@gmail.com> Cc: Christoph Hellwig <hch@lst.de> Cc: iommu@lists.linux-foundation.org Cc: Robin Murphy <robin.murphy@arm.com> Cc: Marek Szyprowski <m.szyprowski@samsung.com> Cc: dm-devel@redhat.com Cc: Mike Snitzer <snitzer@redhat.com> Cc: Alasdair Kergon <agk@redhat.com> Cc: Daniel Vetter <daniel@ffwll.ch> Cc: intel-gfx@lists.freedesktop.org Cc: Joonas Lahtinen <joonas.lahtinen@linux.intel.com> Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com> Cc: dri-devel@lists.freedesktop.org Cc: David Airlie <airlied@linux.ie> Cc: Jani Nikula <jani.nikula@linux.intel.com> Cc: Rodrigo Vivi <rodrigo.vivi@intel.com> Cc: Tom Zanussi <tom.zanussi@linux.intel.com> Cc: Miroslav Benes <mbenes@suse.cz> Cc: linux-arch@vger.kernel.org Link: https://lkml.kernel.org/r/20190425094802.338890064@linutronix.de
2019-04-25 09:45:06 +00:00
ra->trace_len = stack_trace_save(ra->trace, MAX_TRACE, 2);
}
static void __print_stack_trace(struct btrfs_fs_info *fs_info,
struct ref_action *ra)
{
if (ra->trace_len == 0) {
btrfs_err(fs_info, " ref-verify: no stacktrace");
return;
}
btrfs: ref-verify: Simplify stack trace retrieval Replace the indirection through struct stack_trace with an invocation of the storage array based interface. Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Johannes Thumshirn <jthumshirn@suse.de> Reviewed-by: Josh Poimboeuf <jpoimboe@redhat.com> Acked-by: David Sterba <dsterba@suse.com> Cc: Andy Lutomirski <luto@kernel.org> Cc: Chris Mason <clm@fb.com> Cc: Josef Bacik <josef@toxicpanda.com> Cc: linux-btrfs@vger.kernel.org Cc: Steven Rostedt <rostedt@goodmis.org> Cc: Alexander Potapenko <glider@google.com> Cc: Alexey Dobriyan <adobriyan@gmail.com> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Christoph Lameter <cl@linux.com> Cc: Pekka Enberg <penberg@kernel.org> Cc: linux-mm@kvack.org Cc: David Rientjes <rientjes@google.com> Cc: Catalin Marinas <catalin.marinas@arm.com> Cc: Dmitry Vyukov <dvyukov@google.com> Cc: Andrey Ryabinin <aryabinin@virtuozzo.com> Cc: kasan-dev@googlegroups.com Cc: Mike Rapoport <rppt@linux.vnet.ibm.com> Cc: Akinobu Mita <akinobu.mita@gmail.com> Cc: Christoph Hellwig <hch@lst.de> Cc: iommu@lists.linux-foundation.org Cc: Robin Murphy <robin.murphy@arm.com> Cc: Marek Szyprowski <m.szyprowski@samsung.com> Cc: dm-devel@redhat.com Cc: Mike Snitzer <snitzer@redhat.com> Cc: Alasdair Kergon <agk@redhat.com> Cc: Daniel Vetter <daniel@ffwll.ch> Cc: intel-gfx@lists.freedesktop.org Cc: Joonas Lahtinen <joonas.lahtinen@linux.intel.com> Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com> Cc: dri-devel@lists.freedesktop.org Cc: David Airlie <airlied@linux.ie> Cc: Jani Nikula <jani.nikula@linux.intel.com> Cc: Rodrigo Vivi <rodrigo.vivi@intel.com> Cc: Tom Zanussi <tom.zanussi@linux.intel.com> Cc: Miroslav Benes <mbenes@suse.cz> Cc: linux-arch@vger.kernel.org Link: https://lkml.kernel.org/r/20190425094802.338890064@linutronix.de
2019-04-25 09:45:06 +00:00
stack_trace_print(ra->trace, ra->trace_len, 2);
}
#else
static inline void __save_stack_trace(struct ref_action *ra)
{
}
static inline void __print_stack_trace(struct btrfs_fs_info *fs_info,
struct ref_action *ra)
{
btrfs_err(fs_info, " ref-verify: no stacktrace support");
}
#endif
static void free_block_entry(struct block_entry *be)
{
struct root_entry *re;
struct ref_entry *ref;
struct ref_action *ra;
struct rb_node *n;
while ((n = rb_first(&be->roots))) {
re = rb_entry(n, struct root_entry, node);
rb_erase(&re->node, &be->roots);
kfree(re);
}
while((n = rb_first(&be->refs))) {
ref = rb_entry(n, struct ref_entry, node);
rb_erase(&ref->node, &be->refs);
kfree(ref);
}
while (!list_empty(&be->actions)) {
ra = list_first_entry(&be->actions, struct ref_action,
list);
list_del(&ra->list);
kfree(ra);
}
kfree(be);
}
static struct block_entry *add_block_entry(struct btrfs_fs_info *fs_info,
u64 bytenr, u64 len,
u64 root_objectid)
{
struct block_entry *be = NULL, *exist;
struct root_entry *re = NULL;
btrfs: stop doing GFP_KERNEL memory allocations in the ref verify tool In commit 351cbf6e4410e7 ("btrfs: use nofs allocations for running delayed items") we wrapped all btree updates when running delayed items with memalloc_nofs_save() and memalloc_nofs_restore(), due to a lock inversion detected by lockdep involving reclaim and the mutex of delayed nodes. The problem is because the ref verify tool does some memory allocations with GFP_KERNEL, which can trigger reclaim and reclaim can trigger inode eviction, which requires locking the mutex of an inode's delayed node. On the other hand the ref verify tool is called when allocating metadata extents as part of operations that modify a btree, which is a problem when running delayed nodes, where we do btree updates while holding the mutex of a delayed node. This is what caused the lockdep warning. Instead of wrapping every btree update when running delayed nodes, change the ref verify tool to never do GFP_KERNEL allocations, because: 1) We get less repeated code, which at the moment does not even have a comment mentioning why we need to setup the NOFS context, which is a recommended good practice as mentioned at Documentation/core-api/gfp_mask-from-fs-io.rst 2) The ref verify tool is something meant only for debugging and not something that should be enabled on non-debug / non-development kernels; 3) We may have yet more places outside delayed-inode.c where we have similar problem: doing btree updates while holding some lock and then having the GFP_KERNEL memory allocations, from the ref verify tool, trigger reclaim and trying again to acquire the same lock through the reclaim path. Or we could get more such cases in the future, therefore this change prevents getting into similar cases when using the ref verify tool. Curiously most of the memory allocations done by the ref verify tool were already using GFP_NOFS, except a few ones for no apparent reason. Reviewed-by: Josef Bacik <josef@toxicpanda.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-20 15:05:23 +00:00
re = kzalloc(sizeof(struct root_entry), GFP_NOFS);
be = kzalloc(sizeof(struct block_entry), GFP_NOFS);
if (!be || !re) {
kfree(re);
kfree(be);
return ERR_PTR(-ENOMEM);
}
be->bytenr = bytenr;
be->len = len;
re->root_objectid = root_objectid;
re->num_refs = 0;
spin_lock(&fs_info->ref_verify_lock);
exist = insert_block_entry(&fs_info->block_tree, be);
if (exist) {
if (root_objectid) {
struct root_entry *exist_re;
exist_re = insert_root_entry(&exist->roots, re);
if (exist_re)
kfree(re);
} else {
kfree(re);
}
kfree(be);
return exist;
}
be->num_refs = 0;
be->metadata = 0;
be->from_disk = 0;
be->roots = RB_ROOT;
be->refs = RB_ROOT;
INIT_LIST_HEAD(&be->actions);
if (root_objectid)
insert_root_entry(&be->roots, re);
else
kfree(re);
return be;
}
static int add_tree_block(struct btrfs_fs_info *fs_info, u64 ref_root,
u64 parent, u64 bytenr, int level)
{
struct block_entry *be;
struct root_entry *re;
struct ref_entry *ref = NULL, *exist;
btrfs: stop doing GFP_KERNEL memory allocations in the ref verify tool In commit 351cbf6e4410e7 ("btrfs: use nofs allocations for running delayed items") we wrapped all btree updates when running delayed items with memalloc_nofs_save() and memalloc_nofs_restore(), due to a lock inversion detected by lockdep involving reclaim and the mutex of delayed nodes. The problem is because the ref verify tool does some memory allocations with GFP_KERNEL, which can trigger reclaim and reclaim can trigger inode eviction, which requires locking the mutex of an inode's delayed node. On the other hand the ref verify tool is called when allocating metadata extents as part of operations that modify a btree, which is a problem when running delayed nodes, where we do btree updates while holding the mutex of a delayed node. This is what caused the lockdep warning. Instead of wrapping every btree update when running delayed nodes, change the ref verify tool to never do GFP_KERNEL allocations, because: 1) We get less repeated code, which at the moment does not even have a comment mentioning why we need to setup the NOFS context, which is a recommended good practice as mentioned at Documentation/core-api/gfp_mask-from-fs-io.rst 2) The ref verify tool is something meant only for debugging and not something that should be enabled on non-debug / non-development kernels; 3) We may have yet more places outside delayed-inode.c where we have similar problem: doing btree updates while holding some lock and then having the GFP_KERNEL memory allocations, from the ref verify tool, trigger reclaim and trying again to acquire the same lock through the reclaim path. Or we could get more such cases in the future, therefore this change prevents getting into similar cases when using the ref verify tool. Curiously most of the memory allocations done by the ref verify tool were already using GFP_NOFS, except a few ones for no apparent reason. Reviewed-by: Josef Bacik <josef@toxicpanda.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-20 15:05:23 +00:00
ref = kmalloc(sizeof(struct ref_entry), GFP_NOFS);
if (!ref)
return -ENOMEM;
if (parent)
ref->root_objectid = 0;
else
ref->root_objectid = ref_root;
ref->parent = parent;
ref->owner = level;
ref->offset = 0;
ref->num_refs = 1;
be = add_block_entry(fs_info, bytenr, fs_info->nodesize, ref_root);
if (IS_ERR(be)) {
kfree(ref);
return PTR_ERR(be);
}
be->num_refs++;
be->from_disk = 1;
be->metadata = 1;
if (!parent) {
ASSERT(ref_root);
re = lookup_root_entry(&be->roots, ref_root);
ASSERT(re);
re->num_refs++;
}
exist = insert_ref_entry(&be->refs, ref);
if (exist) {
exist->num_refs++;
kfree(ref);
}
spin_unlock(&fs_info->ref_verify_lock);
return 0;
}
static int add_shared_data_ref(struct btrfs_fs_info *fs_info,
u64 parent, u32 num_refs, u64 bytenr,
u64 num_bytes)
{
struct block_entry *be;
struct ref_entry *ref;
btrfs: stop doing GFP_KERNEL memory allocations in the ref verify tool In commit 351cbf6e4410e7 ("btrfs: use nofs allocations for running delayed items") we wrapped all btree updates when running delayed items with memalloc_nofs_save() and memalloc_nofs_restore(), due to a lock inversion detected by lockdep involving reclaim and the mutex of delayed nodes. The problem is because the ref verify tool does some memory allocations with GFP_KERNEL, which can trigger reclaim and reclaim can trigger inode eviction, which requires locking the mutex of an inode's delayed node. On the other hand the ref verify tool is called when allocating metadata extents as part of operations that modify a btree, which is a problem when running delayed nodes, where we do btree updates while holding the mutex of a delayed node. This is what caused the lockdep warning. Instead of wrapping every btree update when running delayed nodes, change the ref verify tool to never do GFP_KERNEL allocations, because: 1) We get less repeated code, which at the moment does not even have a comment mentioning why we need to setup the NOFS context, which is a recommended good practice as mentioned at Documentation/core-api/gfp_mask-from-fs-io.rst 2) The ref verify tool is something meant only for debugging and not something that should be enabled on non-debug / non-development kernels; 3) We may have yet more places outside delayed-inode.c where we have similar problem: doing btree updates while holding some lock and then having the GFP_KERNEL memory allocations, from the ref verify tool, trigger reclaim and trying again to acquire the same lock through the reclaim path. Or we could get more such cases in the future, therefore this change prevents getting into similar cases when using the ref verify tool. Curiously most of the memory allocations done by the ref verify tool were already using GFP_NOFS, except a few ones for no apparent reason. Reviewed-by: Josef Bacik <josef@toxicpanda.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-20 15:05:23 +00:00
ref = kzalloc(sizeof(struct ref_entry), GFP_NOFS);
if (!ref)
return -ENOMEM;
be = add_block_entry(fs_info, bytenr, num_bytes, 0);
if (IS_ERR(be)) {
kfree(ref);
return PTR_ERR(be);
}
be->num_refs += num_refs;
ref->parent = parent;
ref->num_refs = num_refs;
if (insert_ref_entry(&be->refs, ref)) {
spin_unlock(&fs_info->ref_verify_lock);
btrfs_err(fs_info, "existing shared ref when reading from disk?");
kfree(ref);
return -EINVAL;
}
spin_unlock(&fs_info->ref_verify_lock);
return 0;
}
static int add_extent_data_ref(struct btrfs_fs_info *fs_info,
struct extent_buffer *leaf,
struct btrfs_extent_data_ref *dref,
u64 bytenr, u64 num_bytes)
{
struct block_entry *be;
struct ref_entry *ref;
struct root_entry *re;
u64 ref_root = btrfs_extent_data_ref_root(leaf, dref);
u64 owner = btrfs_extent_data_ref_objectid(leaf, dref);
u64 offset = btrfs_extent_data_ref_offset(leaf, dref);
u32 num_refs = btrfs_extent_data_ref_count(leaf, dref);
btrfs: stop doing GFP_KERNEL memory allocations in the ref verify tool In commit 351cbf6e4410e7 ("btrfs: use nofs allocations for running delayed items") we wrapped all btree updates when running delayed items with memalloc_nofs_save() and memalloc_nofs_restore(), due to a lock inversion detected by lockdep involving reclaim and the mutex of delayed nodes. The problem is because the ref verify tool does some memory allocations with GFP_KERNEL, which can trigger reclaim and reclaim can trigger inode eviction, which requires locking the mutex of an inode's delayed node. On the other hand the ref verify tool is called when allocating metadata extents as part of operations that modify a btree, which is a problem when running delayed nodes, where we do btree updates while holding the mutex of a delayed node. This is what caused the lockdep warning. Instead of wrapping every btree update when running delayed nodes, change the ref verify tool to never do GFP_KERNEL allocations, because: 1) We get less repeated code, which at the moment does not even have a comment mentioning why we need to setup the NOFS context, which is a recommended good practice as mentioned at Documentation/core-api/gfp_mask-from-fs-io.rst 2) The ref verify tool is something meant only for debugging and not something that should be enabled on non-debug / non-development kernels; 3) We may have yet more places outside delayed-inode.c where we have similar problem: doing btree updates while holding some lock and then having the GFP_KERNEL memory allocations, from the ref verify tool, trigger reclaim and trying again to acquire the same lock through the reclaim path. Or we could get more such cases in the future, therefore this change prevents getting into similar cases when using the ref verify tool. Curiously most of the memory allocations done by the ref verify tool were already using GFP_NOFS, except a few ones for no apparent reason. Reviewed-by: Josef Bacik <josef@toxicpanda.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2021-07-20 15:05:23 +00:00
ref = kzalloc(sizeof(struct ref_entry), GFP_NOFS);
if (!ref)
return -ENOMEM;
be = add_block_entry(fs_info, bytenr, num_bytes, ref_root);
if (IS_ERR(be)) {
kfree(ref);
return PTR_ERR(be);
}
be->num_refs += num_refs;
ref->parent = 0;
ref->owner = owner;
ref->root_objectid = ref_root;
ref->offset = offset;
ref->num_refs = num_refs;
if (insert_ref_entry(&be->refs, ref)) {
spin_unlock(&fs_info->ref_verify_lock);
btrfs_err(fs_info, "existing ref when reading from disk?");
kfree(ref);
return -EINVAL;
}
re = lookup_root_entry(&be->roots, ref_root);
if (!re) {
spin_unlock(&fs_info->ref_verify_lock);
btrfs_err(fs_info, "missing root in new block entry?");
return -EINVAL;
}
re->num_refs += num_refs;
spin_unlock(&fs_info->ref_verify_lock);
return 0;
}
static int process_extent_item(struct btrfs_fs_info *fs_info,
struct btrfs_path *path, struct btrfs_key *key,
int slot, int *tree_block_level)
{
struct btrfs_extent_item *ei;
struct btrfs_extent_inline_ref *iref;
struct btrfs_extent_data_ref *dref;
struct btrfs_shared_data_ref *sref;
struct extent_buffer *leaf = path->nodes[0];
u32 item_size = btrfs_item_size(leaf, slot);
unsigned long end, ptr;
u64 offset, flags, count;
int type;
int ret = 0;
ei = btrfs_item_ptr(leaf, slot, struct btrfs_extent_item);
flags = btrfs_extent_flags(leaf, ei);
if ((key->type == BTRFS_EXTENT_ITEM_KEY) &&
flags & BTRFS_EXTENT_FLAG_TREE_BLOCK) {
struct btrfs_tree_block_info *info;
info = (struct btrfs_tree_block_info *)(ei + 1);
*tree_block_level = btrfs_tree_block_level(leaf, info);
iref = (struct btrfs_extent_inline_ref *)(info + 1);
} else {
if (key->type == BTRFS_METADATA_ITEM_KEY)
*tree_block_level = key->offset;
iref = (struct btrfs_extent_inline_ref *)(ei + 1);
}
ptr = (unsigned long)iref;
end = (unsigned long)ei + item_size;
while (ptr < end) {
iref = (struct btrfs_extent_inline_ref *)ptr;
type = btrfs_extent_inline_ref_type(leaf, iref);
offset = btrfs_extent_inline_ref_offset(leaf, iref);
switch (type) {
case BTRFS_TREE_BLOCK_REF_KEY:
ret = add_tree_block(fs_info, offset, 0, key->objectid,
*tree_block_level);
break;
case BTRFS_SHARED_BLOCK_REF_KEY:
ret = add_tree_block(fs_info, 0, offset, key->objectid,
*tree_block_level);
break;
case BTRFS_EXTENT_DATA_REF_KEY:
dref = (struct btrfs_extent_data_ref *)(&iref->offset);
ret = add_extent_data_ref(fs_info, leaf, dref,
key->objectid, key->offset);
break;
case BTRFS_SHARED_DATA_REF_KEY:
sref = (struct btrfs_shared_data_ref *)(iref + 1);
count = btrfs_shared_data_ref_count(leaf, sref);
ret = add_shared_data_ref(fs_info, offset, count,
key->objectid, key->offset);
break;
case BTRFS_EXTENT_OWNER_REF_KEY:
if (!btrfs_fs_incompat(fs_info, SIMPLE_QUOTA)) {
btrfs_err(fs_info,
"found extent owner ref without simple quotas enabled");
ret = -EINVAL;
}
break;
default:
btrfs_err(fs_info, "invalid key type in iref");
ret = -EINVAL;
break;
}
if (ret)
break;
ptr += btrfs_extent_inline_ref_size(type);
}
return ret;
}
static int process_leaf(struct btrfs_root *root,
struct btrfs_path *path, u64 *bytenr, u64 *num_bytes,
int *tree_block_level)
{
struct btrfs_fs_info *fs_info = root->fs_info;
struct extent_buffer *leaf = path->nodes[0];
struct btrfs_extent_data_ref *dref;
struct btrfs_shared_data_ref *sref;
u32 count;
int i = 0, ret = 0;
struct btrfs_key key;
int nritems = btrfs_header_nritems(leaf);
for (i = 0; i < nritems; i++) {
btrfs_item_key_to_cpu(leaf, &key, i);
switch (key.type) {
case BTRFS_EXTENT_ITEM_KEY:
*num_bytes = key.offset;
fallthrough;
case BTRFS_METADATA_ITEM_KEY:
*bytenr = key.objectid;
ret = process_extent_item(fs_info, path, &key, i,
tree_block_level);
break;
case BTRFS_TREE_BLOCK_REF_KEY:
ret = add_tree_block(fs_info, key.offset, 0,
key.objectid, *tree_block_level);
break;
case BTRFS_SHARED_BLOCK_REF_KEY:
ret = add_tree_block(fs_info, 0, key.offset,
key.objectid, *tree_block_level);
break;
case BTRFS_EXTENT_DATA_REF_KEY:
dref = btrfs_item_ptr(leaf, i,
struct btrfs_extent_data_ref);
ret = add_extent_data_ref(fs_info, leaf, dref, *bytenr,
*num_bytes);
break;
case BTRFS_SHARED_DATA_REF_KEY:
sref = btrfs_item_ptr(leaf, i,
struct btrfs_shared_data_ref);
count = btrfs_shared_data_ref_count(leaf, sref);
ret = add_shared_data_ref(fs_info, key.offset, count,
*bytenr, *num_bytes);
break;
default:
break;
}
if (ret)
break;
}
return ret;
}
/* Walk down to the leaf from the given level */
static int walk_down_tree(struct btrfs_root *root, struct btrfs_path *path,
int level, u64 *bytenr, u64 *num_bytes,
int *tree_block_level)
{
struct extent_buffer *eb;
int ret = 0;
while (level >= 0) {
if (level) {
eb = btrfs_read_node_slot(path->nodes[level],
path->slots[level]);
if (IS_ERR(eb))
return PTR_ERR(eb);
btrfs_tree_read_lock(eb);
path->nodes[level-1] = eb;
path->slots[level-1] = 0;
path->locks[level-1] = BTRFS_READ_LOCK;
} else {
ret = process_leaf(root, path, bytenr, num_bytes,
tree_block_level);
if (ret)
break;
}
level--;
}
return ret;
}
/* Walk up to the next node that needs to be processed */
static int walk_up_tree(struct btrfs_path *path, int *level)
{
int l;
for (l = 0; l < BTRFS_MAX_LEVEL; l++) {
if (!path->nodes[l])
continue;
if (l) {
path->slots[l]++;
if (path->slots[l] <
btrfs_header_nritems(path->nodes[l])) {
*level = l;
return 0;
}
}
btrfs_tree_unlock_rw(path->nodes[l], path->locks[l]);
free_extent_buffer(path->nodes[l]);
path->nodes[l] = NULL;
path->slots[l] = 0;
path->locks[l] = 0;
}
return 1;
}
static void dump_ref_action(struct btrfs_fs_info *fs_info,
struct ref_action *ra)
{
btrfs_err(fs_info,
" Ref action %d, root %llu, ref_root %llu, parent %llu, owner %llu, offset %llu, num_refs %llu",
ra->action, ra->root, ra->ref.root_objectid, ra->ref.parent,
ra->ref.owner, ra->ref.offset, ra->ref.num_refs);
__print_stack_trace(fs_info, ra);
}
/*
* Dumps all the information from the block entry to printk, it's going to be
* awesome.
*/
static void dump_block_entry(struct btrfs_fs_info *fs_info,
struct block_entry *be)
{
struct ref_entry *ref;
struct root_entry *re;
struct ref_action *ra;
struct rb_node *n;
btrfs_err(fs_info,
"dumping block entry [%llu %llu], num_refs %llu, metadata %d, from disk %d",
be->bytenr, be->len, be->num_refs, be->metadata,
be->from_disk);
for (n = rb_first(&be->refs); n; n = rb_next(n)) {
ref = rb_entry(n, struct ref_entry, node);
btrfs_err(fs_info,
" ref root %llu, parent %llu, owner %llu, offset %llu, num_refs %llu",
ref->root_objectid, ref->parent, ref->owner,
ref->offset, ref->num_refs);
}
for (n = rb_first(&be->roots); n; n = rb_next(n)) {
re = rb_entry(n, struct root_entry, node);
btrfs_err(fs_info, " root entry %llu, num_refs %llu",
re->root_objectid, re->num_refs);
}
list_for_each_entry(ra, &be->actions, list)
dump_ref_action(fs_info, ra);
}
/*
* Called when we modify a ref for a bytenr.
*
* This will add an action item to the given bytenr and do sanity checks to make
* sure we haven't messed something up. If we are making a new allocation and
* this block entry has history we will delete all previous actions as long as
* our sanity checks pass as they are no longer needed.
*/
int btrfs_ref_tree_mod(struct btrfs_fs_info *fs_info,
struct btrfs_ref *generic_ref)
{
struct ref_entry *ref = NULL, *exist;
struct ref_action *ra = NULL;
struct block_entry *be = NULL;
struct root_entry *re = NULL;
int action = generic_ref->action;
int ret = 0;
bool metadata;
u64 bytenr = generic_ref->bytenr;
u64 num_bytes = generic_ref->num_bytes;
u64 parent = generic_ref->parent;
u64 ref_root = 0;
u64 owner = 0;
u64 offset = 0;
if (!btrfs_test_opt(fs_info, REF_VERIFY))
return 0;
if (generic_ref->type == BTRFS_REF_METADATA) {
if (!parent)
ref_root = generic_ref->ref_root;
owner = generic_ref->tree_ref.level;
} else if (!parent) {
ref_root = generic_ref->ref_root;
owner = generic_ref->data_ref.objectid;
offset = generic_ref->data_ref.offset;
}
metadata = owner < BTRFS_FIRST_FREE_OBJECTID;
ref = kzalloc(sizeof(struct ref_entry), GFP_NOFS);
ra = kmalloc(sizeof(struct ref_action), GFP_NOFS);
if (!ra || !ref) {
kfree(ref);
kfree(ra);
ret = -ENOMEM;
goto out;
}
ref->parent = parent;
ref->owner = owner;
ref->root_objectid = ref_root;
ref->offset = offset;
ref->num_refs = (action == BTRFS_DROP_DELAYED_REF) ? -1 : 1;
memcpy(&ra->ref, ref, sizeof(struct ref_entry));
/*
* Save the extra info from the delayed ref in the ref action to make it
* easier to figure out what is happening. The real ref's we add to the
* ref tree need to reflect what we save on disk so it matches any
* on-disk refs we pre-loaded.
*/
ra->ref.owner = owner;
ra->ref.offset = offset;
ra->ref.root_objectid = ref_root;
__save_stack_trace(ra);
INIT_LIST_HEAD(&ra->list);
ra->action = action;
ra->root = generic_ref->real_root;
/*
* This is an allocation, preallocate the block_entry in case we haven't
* used it before.
*/
ret = -EINVAL;
if (action == BTRFS_ADD_DELAYED_EXTENT) {
/*
* For subvol_create we'll just pass in whatever the parent root
* is and the new root objectid, so let's not treat the passed
* in root as if it really has a ref for this bytenr.
*/
be = add_block_entry(fs_info, bytenr, num_bytes, ref_root);
if (IS_ERR(be)) {
kfree(ref);
kfree(ra);
ret = PTR_ERR(be);
goto out;
}
be->num_refs++;
if (metadata)
be->metadata = 1;
if (be->num_refs != 1) {
btrfs_err(fs_info,
"re-allocated a block that still has references to it!");
dump_block_entry(fs_info, be);
dump_ref_action(fs_info, ra);
kfree(ref);
kfree(ra);
goto out_unlock;
}
while (!list_empty(&be->actions)) {
struct ref_action *tmp;
tmp = list_first_entry(&be->actions, struct ref_action,
list);
list_del(&tmp->list);
kfree(tmp);
}
} else {
struct root_entry *tmp;
if (!parent) {
re = kmalloc(sizeof(struct root_entry), GFP_NOFS);
if (!re) {
kfree(ref);
kfree(ra);
ret = -ENOMEM;
goto out;
}
/*
* This is the root that is modifying us, so it's the
* one we want to lookup below when we modify the
* re->num_refs.
*/
ref_root = generic_ref->real_root;
re->root_objectid = generic_ref->real_root;
re->num_refs = 0;
}
spin_lock(&fs_info->ref_verify_lock);
be = lookup_block_entry(&fs_info->block_tree, bytenr);
if (!be) {
btrfs_err(fs_info,
"trying to do action %d to bytenr %llu num_bytes %llu but there is no existing entry!",
action, bytenr, num_bytes);
dump_ref_action(fs_info, ra);
kfree(ref);
kfree(ra);
kfree(re);
goto out_unlock;
} else if (be->num_refs == 0) {
btrfs_err(fs_info,
"trying to do action %d for a bytenr that has 0 total references",
action);
dump_block_entry(fs_info, be);
dump_ref_action(fs_info, ra);
kfree(ref);
kfree(ra);
kfree(re);
goto out_unlock;
}
if (!parent) {
tmp = insert_root_entry(&be->roots, re);
if (tmp) {
kfree(re);
re = tmp;
}
}
}
exist = insert_ref_entry(&be->refs, ref);
if (exist) {
if (action == BTRFS_DROP_DELAYED_REF) {
if (exist->num_refs == 0) {
btrfs_err(fs_info,
"dropping a ref for a existing root that doesn't have a ref on the block");
dump_block_entry(fs_info, be);
dump_ref_action(fs_info, ra);
kfree(ref);
kfree(ra);
goto out_unlock;
}
exist->num_refs--;
if (exist->num_refs == 0) {
rb_erase(&exist->node, &be->refs);
kfree(exist);
}
} else if (!be->metadata) {
exist->num_refs++;
} else {
btrfs_err(fs_info,
"attempting to add another ref for an existing ref on a tree block");
dump_block_entry(fs_info, be);
dump_ref_action(fs_info, ra);
kfree(ref);
kfree(ra);
goto out_unlock;
}
kfree(ref);
} else {
if (action == BTRFS_DROP_DELAYED_REF) {
btrfs_err(fs_info,
"dropping a ref for a root that doesn't have a ref on the block");
dump_block_entry(fs_info, be);
dump_ref_action(fs_info, ra);
btrfs: ref-verify: fix use-after-free after invalid ref action At btrfs_ref_tree_mod() after we successfully inserted the new ref entry (local variable 'ref') into the respective block entry's rbtree (local variable 'be'), if we find an unexpected action of BTRFS_DROP_DELAYED_REF, we error out and free the ref entry without removing it from the block entry's rbtree. Then in the error path of btrfs_ref_tree_mod() we call btrfs_free_ref_cache(), which iterates over all block entries and then calls free_block_entry() for each one, and there we will trigger a use-after-free when we are called against the block entry to which we added the freed ref entry to its rbtree, since the rbtree still points to the block entry, as we didn't remove it from the rbtree before freeing it in the error path at btrfs_ref_tree_mod(). Fix this by removing the new ref entry from the rbtree before freeing it. Syzbot report this with the following stack traces: BTRFS error (device loop0 state EA): Ref action 2, root 5, ref_root 0, parent 8564736, owner 0, offset 0, num_refs 18446744073709551615 __btrfs_mod_ref+0x7dd/0xac0 fs/btrfs/extent-tree.c:2523 update_ref_for_cow+0x9cd/0x11f0 fs/btrfs/ctree.c:512 btrfs_force_cow_block+0x9f6/0x1da0 fs/btrfs/ctree.c:594 btrfs_cow_block+0x35e/0xa40 fs/btrfs/ctree.c:754 btrfs_search_slot+0xbdd/0x30d0 fs/btrfs/ctree.c:2116 btrfs_insert_empty_items+0x9c/0x1a0 fs/btrfs/ctree.c:4314 btrfs_insert_empty_item fs/btrfs/ctree.h:669 [inline] btrfs_insert_orphan_item+0x1f1/0x320 fs/btrfs/orphan.c:23 btrfs_orphan_add+0x6d/0x1a0 fs/btrfs/inode.c:3482 btrfs_unlink+0x267/0x350 fs/btrfs/inode.c:4293 vfs_unlink+0x365/0x650 fs/namei.c:4469 do_unlinkat+0x4ae/0x830 fs/namei.c:4533 __do_sys_unlinkat fs/namei.c:4576 [inline] __se_sys_unlinkat fs/namei.c:4569 [inline] __x64_sys_unlinkat+0xcc/0xf0 fs/namei.c:4569 do_syscall_x64 arch/x86/entry/common.c:52 [inline] do_syscall_64+0xf3/0x230 arch/x86/entry/common.c:83 entry_SYSCALL_64_after_hwframe+0x77/0x7f BTRFS error (device loop0 state EA): Ref action 1, root 5, ref_root 5, parent 0, owner 260, offset 0, num_refs 1 __btrfs_mod_ref+0x76b/0xac0 fs/btrfs/extent-tree.c:2521 update_ref_for_cow+0x96a/0x11f0 btrfs_force_cow_block+0x9f6/0x1da0 fs/btrfs/ctree.c:594 btrfs_cow_block+0x35e/0xa40 fs/btrfs/ctree.c:754 btrfs_search_slot+0xbdd/0x30d0 fs/btrfs/ctree.c:2116 btrfs_lookup_inode+0xdc/0x480 fs/btrfs/inode-item.c:411 __btrfs_update_delayed_inode+0x1e7/0xb90 fs/btrfs/delayed-inode.c:1030 btrfs_update_delayed_inode fs/btrfs/delayed-inode.c:1114 [inline] __btrfs_commit_inode_delayed_items+0x2318/0x24a0 fs/btrfs/delayed-inode.c:1137 __btrfs_run_delayed_items+0x213/0x490 fs/btrfs/delayed-inode.c:1171 btrfs_commit_transaction+0x8a8/0x3740 fs/btrfs/transaction.c:2313 prepare_to_relocate+0x3c4/0x4c0 fs/btrfs/relocation.c:3586 relocate_block_group+0x16c/0xd40 fs/btrfs/relocation.c:3611 btrfs_relocate_block_group+0x77d/0xd90 fs/btrfs/relocation.c:4081 btrfs_relocate_chunk+0x12c/0x3b0 fs/btrfs/volumes.c:3377 __btrfs_balance+0x1b0f/0x26b0 fs/btrfs/volumes.c:4161 btrfs_balance+0xbdc/0x10c0 fs/btrfs/volumes.c:4538 BTRFS error (device loop0 state EA): Ref action 2, root 5, ref_root 0, parent 8564736, owner 0, offset 0, num_refs 18446744073709551615 __btrfs_mod_ref+0x7dd/0xac0 fs/btrfs/extent-tree.c:2523 update_ref_for_cow+0x9cd/0x11f0 fs/btrfs/ctree.c:512 btrfs_force_cow_block+0x9f6/0x1da0 fs/btrfs/ctree.c:594 btrfs_cow_block+0x35e/0xa40 fs/btrfs/ctree.c:754 btrfs_search_slot+0xbdd/0x30d0 fs/btrfs/ctree.c:2116 btrfs_lookup_inode+0xdc/0x480 fs/btrfs/inode-item.c:411 __btrfs_update_delayed_inode+0x1e7/0xb90 fs/btrfs/delayed-inode.c:1030 btrfs_update_delayed_inode fs/btrfs/delayed-inode.c:1114 [inline] __btrfs_commit_inode_delayed_items+0x2318/0x24a0 fs/btrfs/delayed-inode.c:1137 __btrfs_run_delayed_items+0x213/0x490 fs/btrfs/delayed-inode.c:1171 btrfs_commit_transaction+0x8a8/0x3740 fs/btrfs/transaction.c:2313 prepare_to_relocate+0x3c4/0x4c0 fs/btrfs/relocation.c:3586 relocate_block_group+0x16c/0xd40 fs/btrfs/relocation.c:3611 btrfs_relocate_block_group+0x77d/0xd90 fs/btrfs/relocation.c:4081 btrfs_relocate_chunk+0x12c/0x3b0 fs/btrfs/volumes.c:3377 __btrfs_balance+0x1b0f/0x26b0 fs/btrfs/volumes.c:4161 btrfs_balance+0xbdc/0x10c0 fs/btrfs/volumes.c:4538 ================================================================== BUG: KASAN: slab-use-after-free in rb_first+0x69/0x70 lib/rbtree.c:473 Read of size 8 at addr ffff888042d1af38 by task syz.0.0/5329 CPU: 0 UID: 0 PID: 5329 Comm: syz.0.0 Not tainted 6.12.0-rc7-syzkaller #0 Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2~bpo12+1 04/01/2014 Call Trace: <TASK> __dump_stack lib/dump_stack.c:94 [inline] dump_stack_lvl+0x241/0x360 lib/dump_stack.c:120 print_address_description mm/kasan/report.c:377 [inline] print_report+0x169/0x550 mm/kasan/report.c:488 kasan_report+0x143/0x180 mm/kasan/report.c:601 rb_first+0x69/0x70 lib/rbtree.c:473 free_block_entry+0x78/0x230 fs/btrfs/ref-verify.c:248 btrfs_free_ref_cache+0xa3/0x100 fs/btrfs/ref-verify.c:917 btrfs_ref_tree_mod+0x139f/0x15e0 fs/btrfs/ref-verify.c:898 btrfs_free_extent+0x33c/0x380 fs/btrfs/extent-tree.c:3544 __btrfs_mod_ref+0x7dd/0xac0 fs/btrfs/extent-tree.c:2523 update_ref_for_cow+0x9cd/0x11f0 fs/btrfs/ctree.c:512 btrfs_force_cow_block+0x9f6/0x1da0 fs/btrfs/ctree.c:594 btrfs_cow_block+0x35e/0xa40 fs/btrfs/ctree.c:754 btrfs_search_slot+0xbdd/0x30d0 fs/btrfs/ctree.c:2116 btrfs_lookup_inode+0xdc/0x480 fs/btrfs/inode-item.c:411 __btrfs_update_delayed_inode+0x1e7/0xb90 fs/btrfs/delayed-inode.c:1030 btrfs_update_delayed_inode fs/btrfs/delayed-inode.c:1114 [inline] __btrfs_commit_inode_delayed_items+0x2318/0x24a0 fs/btrfs/delayed-inode.c:1137 __btrfs_run_delayed_items+0x213/0x490 fs/btrfs/delayed-inode.c:1171 btrfs_commit_transaction+0x8a8/0x3740 fs/btrfs/transaction.c:2313 prepare_to_relocate+0x3c4/0x4c0 fs/btrfs/relocation.c:3586 relocate_block_group+0x16c/0xd40 fs/btrfs/relocation.c:3611 btrfs_relocate_block_group+0x77d/0xd90 fs/btrfs/relocation.c:4081 btrfs_relocate_chunk+0x12c/0x3b0 fs/btrfs/volumes.c:3377 __btrfs_balance+0x1b0f/0x26b0 fs/btrfs/volumes.c:4161 btrfs_balance+0xbdc/0x10c0 fs/btrfs/volumes.c:4538 btrfs_ioctl_balance+0x493/0x7c0 fs/btrfs/ioctl.c:3673 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:907 [inline] __se_sys_ioctl+0xf9/0x170 fs/ioctl.c:893 do_syscall_x64 arch/x86/entry/common.c:52 [inline] do_syscall_64+0xf3/0x230 arch/x86/entry/common.c:83 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f996df7e719 RSP: 002b:00007f996ede7038 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 RAX: ffffffffffffffda RBX: 00007f996e135f80 RCX: 00007f996df7e719 RDX: 0000000020000180 RSI: 00000000c4009420 RDI: 0000000000000004 RBP: 00007f996dff139e R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 0000000000000000 R14: 00007f996e135f80 R15: 00007fff79f32e68 </TASK> Allocated by task 5329: kasan_save_stack mm/kasan/common.c:47 [inline] kasan_save_track+0x3f/0x80 mm/kasan/common.c:68 poison_kmalloc_redzone mm/kasan/common.c:377 [inline] __kasan_kmalloc+0x98/0xb0 mm/kasan/common.c:394 kasan_kmalloc include/linux/kasan.h:257 [inline] __kmalloc_cache_noprof+0x19c/0x2c0 mm/slub.c:4295 kmalloc_noprof include/linux/slab.h:878 [inline] kzalloc_noprof include/linux/slab.h:1014 [inline] btrfs_ref_tree_mod+0x264/0x15e0 fs/btrfs/ref-verify.c:701 btrfs_free_extent+0x33c/0x380 fs/btrfs/extent-tree.c:3544 __btrfs_mod_ref+0x7dd/0xac0 fs/btrfs/extent-tree.c:2523 update_ref_for_cow+0x9cd/0x11f0 fs/btrfs/ctree.c:512 btrfs_force_cow_block+0x9f6/0x1da0 fs/btrfs/ctree.c:594 btrfs_cow_block+0x35e/0xa40 fs/btrfs/ctree.c:754 btrfs_search_slot+0xbdd/0x30d0 fs/btrfs/ctree.c:2116 btrfs_lookup_inode+0xdc/0x480 fs/btrfs/inode-item.c:411 __btrfs_update_delayed_inode+0x1e7/0xb90 fs/btrfs/delayed-inode.c:1030 btrfs_update_delayed_inode fs/btrfs/delayed-inode.c:1114 [inline] __btrfs_commit_inode_delayed_items+0x2318/0x24a0 fs/btrfs/delayed-inode.c:1137 __btrfs_run_delayed_items+0x213/0x490 fs/btrfs/delayed-inode.c:1171 btrfs_commit_transaction+0x8a8/0x3740 fs/btrfs/transaction.c:2313 prepare_to_relocate+0x3c4/0x4c0 fs/btrfs/relocation.c:3586 relocate_block_group+0x16c/0xd40 fs/btrfs/relocation.c:3611 btrfs_relocate_block_group+0x77d/0xd90 fs/btrfs/relocation.c:4081 btrfs_relocate_chunk+0x12c/0x3b0 fs/btrfs/volumes.c:3377 __btrfs_balance+0x1b0f/0x26b0 fs/btrfs/volumes.c:4161 btrfs_balance+0xbdc/0x10c0 fs/btrfs/volumes.c:4538 btrfs_ioctl_balance+0x493/0x7c0 fs/btrfs/ioctl.c:3673 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:907 [inline] __se_sys_ioctl+0xf9/0x170 fs/ioctl.c:893 do_syscall_x64 arch/x86/entry/common.c:52 [inline] do_syscall_64+0xf3/0x230 arch/x86/entry/common.c:83 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 5329: kasan_save_stack mm/kasan/common.c:47 [inline] kasan_save_track+0x3f/0x80 mm/kasan/common.c:68 kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:579 poison_slab_object mm/kasan/common.c:247 [inline] __kasan_slab_free+0x59/0x70 mm/kasan/common.c:264 kasan_slab_free include/linux/kasan.h:230 [inline] slab_free_hook mm/slub.c:2342 [inline] slab_free mm/slub.c:4579 [inline] kfree+0x1a0/0x440 mm/slub.c:4727 btrfs_ref_tree_mod+0x136c/0x15e0 btrfs_free_extent+0x33c/0x380 fs/btrfs/extent-tree.c:3544 __btrfs_mod_ref+0x7dd/0xac0 fs/btrfs/extent-tree.c:2523 update_ref_for_cow+0x9cd/0x11f0 fs/btrfs/ctree.c:512 btrfs_force_cow_block+0x9f6/0x1da0 fs/btrfs/ctree.c:594 btrfs_cow_block+0x35e/0xa40 fs/btrfs/ctree.c:754 btrfs_search_slot+0xbdd/0x30d0 fs/btrfs/ctree.c:2116 btrfs_lookup_inode+0xdc/0x480 fs/btrfs/inode-item.c:411 __btrfs_update_delayed_inode+0x1e7/0xb90 fs/btrfs/delayed-inode.c:1030 btrfs_update_delayed_inode fs/btrfs/delayed-inode.c:1114 [inline] __btrfs_commit_inode_delayed_items+0x2318/0x24a0 fs/btrfs/delayed-inode.c:1137 __btrfs_run_delayed_items+0x213/0x490 fs/btrfs/delayed-inode.c:1171 btrfs_commit_transaction+0x8a8/0x3740 fs/btrfs/transaction.c:2313 prepare_to_relocate+0x3c4/0x4c0 fs/btrfs/relocation.c:3586 relocate_block_group+0x16c/0xd40 fs/btrfs/relocation.c:3611 btrfs_relocate_block_group+0x77d/0xd90 fs/btrfs/relocation.c:4081 btrfs_relocate_chunk+0x12c/0x3b0 fs/btrfs/volumes.c:3377 __btrfs_balance+0x1b0f/0x26b0 fs/btrfs/volumes.c:4161 btrfs_balance+0xbdc/0x10c0 fs/btrfs/volumes.c:4538 btrfs_ioctl_balance+0x493/0x7c0 fs/btrfs/ioctl.c:3673 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:907 [inline] __se_sys_ioctl+0xf9/0x170 fs/ioctl.c:893 do_syscall_x64 arch/x86/entry/common.c:52 [inline] do_syscall_64+0xf3/0x230 arch/x86/entry/common.c:83 entry_SYSCALL_64_after_hwframe+0x77/0x7f The buggy address belongs to the object at ffff888042d1af00 which belongs to the cache kmalloc-64 of size 64 The buggy address is located 56 bytes inside of freed 64-byte region [ffff888042d1af00, ffff888042d1af40) The buggy address belongs to the physical page: page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x42d1a anon flags: 0x4fff00000000000(node=1|zone=1|lastcpupid=0x7ff) page_type: f5(slab) raw: 04fff00000000000 ffff88801ac418c0 0000000000000000 dead000000000001 raw: 0000000000000000 0000000000200020 00000001f5000000 0000000000000000 page dumped because: kasan: bad access detected page_owner tracks the page as allocated page last allocated via order 0, migratetype Unmovable, gfp_mask 0x52c40(GFP_NOFS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP), pid 5055, tgid 5055 (dhcpcd-run-hook), ts 40377240074, free_ts 40376848335 set_page_owner include/linux/page_owner.h:32 [inline] post_alloc_hook+0x1f3/0x230 mm/page_alloc.c:1541 prep_new_page mm/page_alloc.c:1549 [inline] get_page_from_freelist+0x3649/0x3790 mm/page_alloc.c:3459 __alloc_pages_noprof+0x292/0x710 mm/page_alloc.c:4735 alloc_pages_mpol_noprof+0x3e8/0x680 mm/mempolicy.c:2265 alloc_slab_page+0x6a/0x140 mm/slub.c:2412 allocate_slab+0x5a/0x2f0 mm/slub.c:2578 new_slab mm/slub.c:2631 [inline] ___slab_alloc+0xcd1/0x14b0 mm/slub.c:3818 __slab_alloc+0x58/0xa0 mm/slub.c:3908 __slab_alloc_node mm/slub.c:3961 [inline] slab_alloc_node mm/slub.c:4122 [inline] __do_kmalloc_node mm/slub.c:4263 [inline] __kmalloc_noprof+0x25a/0x400 mm/slub.c:4276 kmalloc_noprof include/linux/slab.h:882 [inline] kzalloc_noprof include/linux/slab.h:1014 [inline] tomoyo_encode2 security/tomoyo/realpath.c:45 [inline] tomoyo_encode+0x26f/0x540 security/tomoyo/realpath.c:80 tomoyo_realpath_from_path+0x59e/0x5e0 security/tomoyo/realpath.c:283 tomoyo_get_realpath security/tomoyo/file.c:151 [inline] tomoyo_check_open_permission+0x255/0x500 security/tomoyo/file.c:771 security_file_open+0x777/0x990 security/security.c:3109 do_dentry_open+0x369/0x1460 fs/open.c:945 vfs_open+0x3e/0x330 fs/open.c:1088 do_open fs/namei.c:3774 [inline] path_openat+0x2c84/0x3590 fs/namei.c:3933 page last free pid 5055 tgid 5055 stack trace: reset_page_owner include/linux/page_owner.h:25 [inline] free_pages_prepare mm/page_alloc.c:1112 [inline] free_unref_page+0xcfb/0xf20 mm/page_alloc.c:2642 free_pipe_info+0x300/0x390 fs/pipe.c:860 put_pipe_info fs/pipe.c:719 [inline] pipe_release+0x245/0x320 fs/pipe.c:742 __fput+0x23f/0x880 fs/file_table.c:431 __do_sys_close fs/open.c:1567 [inline] __se_sys_close fs/open.c:1552 [inline] __x64_sys_close+0x7f/0x110 fs/open.c:1552 do_syscall_x64 arch/x86/entry/common.c:52 [inline] do_syscall_64+0xf3/0x230 arch/x86/entry/common.c:83 entry_SYSCALL_64_after_hwframe+0x77/0x7f Memory state around the buggy address: ffff888042d1ae00: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc ffff888042d1ae80: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc >ffff888042d1af00: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc ^ ffff888042d1af80: 00 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc ffff888042d1b000: 00 00 00 00 00 fc fc 00 00 00 00 00 fc fc 00 00 Reported-by: syzbot+7325f164162e200000c1@syzkaller.appspotmail.com Link: https://lore.kernel.org/linux-btrfs/673723eb.050a0220.1324f8.00a8.GAE@google.com/T/#u Fixes: fd708b81d972 ("Btrfs: add a extent ref verify tool") CC: stable@vger.kernel.org # 4.19+ Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2024-11-15 11:29:21 +00:00
rb_erase(&ref->node, &be->refs);
kfree(ref);
kfree(ra);
goto out_unlock;
}
}
if (!parent && !re) {
re = lookup_root_entry(&be->roots, ref_root);
if (!re) {
/*
* This shouldn't happen because we will add our re
* above when we lookup the be with !parent, but just in
* case catch this case so we don't panic because I
* didn't think of some other corner case.
*/
btrfs_err(fs_info, "failed to find root %llu for %llu",
generic_ref->real_root, be->bytenr);
dump_block_entry(fs_info, be);
dump_ref_action(fs_info, ra);
kfree(ra);
goto out_unlock;
}
}
if (action == BTRFS_DROP_DELAYED_REF) {
if (re)
re->num_refs--;
be->num_refs--;
} else if (action == BTRFS_ADD_DELAYED_REF) {
be->num_refs++;
if (re)
re->num_refs++;
}
list_add_tail(&ra->list, &be->actions);
ret = 0;
out_unlock:
spin_unlock(&fs_info->ref_verify_lock);
out:
if (ret) {
btrfs_free_ref_cache(fs_info);
btrfs_clear_opt(fs_info->mount_opt, REF_VERIFY);
}
return ret;
}
/* Free up the ref cache */
void btrfs_free_ref_cache(struct btrfs_fs_info *fs_info)
{
struct block_entry *be;
struct rb_node *n;
if (!btrfs_test_opt(fs_info, REF_VERIFY))
return;
spin_lock(&fs_info->ref_verify_lock);
while ((n = rb_first(&fs_info->block_tree))) {
be = rb_entry(n, struct block_entry, node);
rb_erase(&be->node, &fs_info->block_tree);
free_block_entry(be);
cond_resched_lock(&fs_info->ref_verify_lock);
}
spin_unlock(&fs_info->ref_verify_lock);
}
void btrfs_free_ref_tree_range(struct btrfs_fs_info *fs_info, u64 start,
u64 len)
{
struct block_entry *be = NULL, *entry;
struct rb_node *n;
if (!btrfs_test_opt(fs_info, REF_VERIFY))
return;
spin_lock(&fs_info->ref_verify_lock);
n = fs_info->block_tree.rb_node;
while (n) {
entry = rb_entry(n, struct block_entry, node);
if (entry->bytenr < start) {
n = n->rb_right;
} else if (entry->bytenr > start) {
n = n->rb_left;
} else {
be = entry;
break;
}
/* We want to get as close to start as possible */
if (be == NULL ||
(entry->bytenr < start && be->bytenr > start) ||
(entry->bytenr < start && entry->bytenr > be->bytenr))
be = entry;
}
/*
* Could have an empty block group, maybe have something to check for
* this case to verify we were actually empty?
*/
if (!be) {
spin_unlock(&fs_info->ref_verify_lock);
return;
}
n = &be->node;
while (n) {
be = rb_entry(n, struct block_entry, node);
n = rb_next(n);
if (be->bytenr < start && be->bytenr + be->len > start) {
btrfs_err(fs_info,
"block entry overlaps a block group [%llu,%llu]!",
start, len);
dump_block_entry(fs_info, be);
continue;
}
if (be->bytenr < start)
continue;
if (be->bytenr >= start + len)
break;
if (be->bytenr + be->len > start + len) {
btrfs_err(fs_info,
"block entry overlaps a block group [%llu,%llu]!",
start, len);
dump_block_entry(fs_info, be);
}
rb_erase(&be->node, &fs_info->block_tree);
free_block_entry(be);
}
spin_unlock(&fs_info->ref_verify_lock);
}
/* Walk down all roots and build the ref tree, meant to be called at mount */
int btrfs_build_ref_tree(struct btrfs_fs_info *fs_info)
{
struct btrfs_root *extent_root;
struct btrfs_path *path;
struct extent_buffer *eb;
int tree_block_level = 0;
u64 bytenr = 0, num_bytes = 0;
int ret, level;
if (!btrfs_test_opt(fs_info, REF_VERIFY))
return 0;
path = btrfs_alloc_path();
if (!path)
return -ENOMEM;
extent_root = btrfs_extent_root(fs_info, 0);
eb = btrfs_read_lock_root_node(extent_root);
level = btrfs_header_level(eb);
path->nodes[level] = eb;
path->slots[level] = 0;
path->locks[level] = BTRFS_READ_LOCK;
while (1) {
/*
* We have to keep track of the bytenr/num_bytes we last hit
* because we could have run out of space for an inline ref, and
* would have had to added a ref key item which may appear on a
* different leaf from the original extent item.
*/
ret = walk_down_tree(extent_root, path, level,
&bytenr, &num_bytes, &tree_block_level);
if (ret)
break;
ret = walk_up_tree(path, &level);
if (ret < 0)
break;
if (ret > 0) {
ret = 0;
break;
}
}
if (ret) {
btrfs_free_ref_cache(fs_info);
btrfs_clear_opt(fs_info->mount_opt, REF_VERIFY);
}
btrfs_free_path(path);
return ret;
}