Skip to content
  1. Apr 21, 2021
    • Filipe Manana's avatar
      btrfs: fix metadata extent leak after failure to create subvolume · 67addf29
      Filipe Manana authored
      
      
      When creating a subvolume we allocate an extent buffer for its root node
      after starting a transaction. We setup a root item for the subvolume that
      points to that extent buffer and then attempt to insert the root item into
      the root tree - however if that fails, due to ENOMEM for example, we do
      not free the extent buffer previously allocated and we do not abort the
      transaction (as at that point we did nothing that can not be undone).
      
      This means that we effectively do not return the metadata extent back to
      the free space cache/tree and we leave a delayed reference for it which
      causes a metadata extent item to be added to the extent tree, in the next
      transaction commit, without having backreferences. When this happens
      'btrfs check' reports the following:
      
        $ btrfs check /dev/sdi
        Opening filesystem to check...
        Checking filesystem on /dev/sdi
        UUID: dce2cb9d-025f-4b05-a4bf-cee0ad3785eb
        [1/7] checking root items
        [2/7] checking extents
        ref mismatch on [30425088 16384] extent item 1, found 0
        backref 30425088 root 256 not referenced back 0x564a91c23d70
        incorrect global backref count on 30425088 found 1 wanted 0
        backpointer mismatch on [30425088 16384]
        owner ref check failed [30425088 16384]
        ERROR: errors found in extent allocation tree or chunk allocation
        [3/7] checking free space cache
        [4/7] checking fs roots
        [5/7] checking only csums items (without verifying data)
        [6/7] checking root refs
        [7/7] checking quota groups skipped (not enabled on this FS)
        found 212992 bytes used, error(s) found
        total csum bytes: 0
        total tree bytes: 131072
        total fs tree bytes: 32768
        total extent tree bytes: 16384
        btree space waste bytes: 124669
        file data blocks allocated: 65536
         referenced 65536
      
      So fix this by freeing the metadata extent if btrfs_insert_root() returns
      an error.
      
      CC: stable@vger.kernel.org # 4.4+
      Signed-off-by: default avatarFilipe Manana <fdmanana@suse.com>
      Reviewed-by: default avatarDavid Sterba <dsterba@suse.com>
      Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
      67addf29
  2. Apr 20, 2021
    • Qu Wenruo's avatar
      btrfs: handle remount to no compress during compression · 1d8ba9e7
      Qu Wenruo authored
      [BUG]
      When running btrfs/071 with inode_need_compress() removed from
      compress_file_range(), we got the following crash:
      
        BUG: kernel NULL pointer dereference, address: 0000000000000018
        #PF: supervisor read access in kernel mode
        #PF: error_code(0x0000) - not-present page
        Workqueue: btrfs-delalloc btrfs_work_helper [btrfs]
        RIP: 0010:compress_file_range+0x476/0x7b0 [btrfs]
        Call Trace:
         ? submit_compressed_extents+0x450/0x450 [btrfs]
         async_cow_start+0x16/0x40 [btrfs]
         btrfs_work_helper+0xf2/0x3e0 [btrfs]
         process_one_work+0x278/0x5e0
         worker_thread+0x55/0x400
         ? process_one_work+0x5e0/0x5e0
         kthread+0x168/0x190
         ? kthread_create_worker_on_cpu+0x70/0x70
         ret_from_fork+0x22/0x30
        ---[ end trace 65faf4eae941fa7d ]---
      
      This is already after the patch "btrfs: inode: fix NULL pointer
      dereference if inode doesn't need compression."
      
      [CAUSE]
      @pages is firstly created by kcalloc() in compress_file_extent():
                      pages = kcalloc(nr_pages, sizeof(struct page *), GFP_NOFS);
      
      Then passed to btrfs_compress_pages() to be utilized there:
      
                      ret = btrfs_compress_pages(...
                                                 pages,
                                                 &nr_pages,
                                                 ...);
      
      btrfs_compress_pages() will initialize each page as output, in
      zlib_compress_pages() we have:
      
                              pages[nr_pages] = out_page;
                              nr_pages++;
      
      Normally this is completely fine, but there is a special case which
      is in btrfs_compress_pages() itself:
      
              switch (type) {
              default:
                      return -E2BIG;
              }
      
      In this case, we didn't modify @pages nor @out_pages, leaving them
      untouched, then when we cleanup pages, the we can hit NULL pointer
      dereference again:
      
              if (pages) {
                      for (i = 0; i < nr_pages; i++) {
                              WARN_ON(pages[i]->mapping);
                              put_page(pages[i]);
                      }
              ...
              }
      
      Since pages[i] are all initialized to zero, and btrfs_compress_pages()
      doesn't change them at all, accessing pages[i]->mapping would lead to
      NULL pointer dereference.
      
      This is not possible for current kernel, as we check
      inode_need_compress() before doing pages allocation.
      But if we're going to remove that inode_need_compress() in
      compress_file_extent(), then it's going to be a problem.
      
      [FIX]
      When btrfs_compress_pages() hits its default case, modify @out_pages to
      0 to prevent such problem from happening.
      
      Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=212331
      
      
      CC: stable@vger.kernel.org # 5.10+
      Reviewed-by: default avatarJosef Bacik <josef@toxicpanda.com>
      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>
      1d8ba9e7
  3. Apr 19, 2021