From patchwork Fri May 11 06:34:40 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: robbieko X-Patchwork-Id: 10393349 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id E4EE660348 for ; Fri, 11 May 2018 06:35:00 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 14E6B28B4B for ; Fri, 11 May 2018 06:35:01 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 06C3028E0D; Fri, 11 May 2018 06:35:01 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI, T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 86FA628B4B for ; Fri, 11 May 2018 06:34:59 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752380AbeEKGe5 (ORCPT ); Fri, 11 May 2018 02:34:57 -0400 Received: from synology.com ([59.124.61.242]:43470 "EHLO synology.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752352AbeEKGe4 (ORCPT ); Fri, 11 May 2018 02:34:56 -0400 Received: from localhost.localdomain (unknown [10.13.20.241]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-SHA256 (128/128 bits)) (No client certificate requested) by synology.com (Postfix) with ESMTPSA id 946221F410430; Fri, 11 May 2018 14:34:54 +0800 (CST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=synology.com; s=123; t=1526020494; bh=nybyp3THZX36YGkJl9oBNS959v+Y/gZuvhruWDdxH5E=; h=From:To:Cc:Subject:Date; b=YBY5/6RcSowpsEqWiVuT1vbdXh8izrON388f44a3s9BA4wpTNkuGLRHocvCXhbmGU n2f+Qs9kya4eLwyGMGwua+B2iW+YN8sAteTDqIESDHETjqdRckgDz0Nd0ZS6malkE4 hBE6dniWUZ2LxHuzupbBWhepoKYgxYMnCiuFfUuI= From: robbieko To: linux-btrfs@vger.kernel.org Cc: Robbie Ko Subject: [PATCH v2] btrfs: incremental send, fix BUG when invalid memory access Date: Fri, 11 May 2018 14:34:40 +0800 Message-Id: <1526020480-18466-1-git-send-email-robbieko@synology.com> X-Mailer: git-send-email 1.9.1 X-Synology-MCP-Status: no X-Synology-Spam-Flag: no X-Synology-Spam-Status: score=0, required 5, WHITELIST_FROM_ADDRESS 0 X-Synology-Virus-Status: no Sender: linux-btrfs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-btrfs@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Robbie Ko [BUG] btrfs incremental send BUG happens when creating a snapshot of snapshot that is being used by send. [REASON] The problem can happen if while we are doing a send one of the snapshots used (parent or send) is snapshotted, because snapshoting implies COWing the root of the source subvolume/snaphot. 1. When send with the parent, the send process will get the commit roots from parent and send, and add references by extent_buffer_get. 2. When the snapshots(parent or send) is snapshotted, the committed root of the snapshot will be modified, because snapshoting implies COWing the root of the source subvolume/snaphot. 3. When COWing, we will allocate new space to submit root and release the old space. Assume that A is the old commit root. __btrfs_cow_block() --btrfs_free_tree_block() ----btrfs_add_free_space(bytenr of A) 4. Therefore, the old commit_root space can be used when other processes need to allocate new treeblocks. However, alloc_extent_buffer is created by the bytenr. It will first find out if there is an existing extent_buffer through find_extent_buffer and cause the original extent_buffer to be modified. btrfs_alloc_tree_block --btrfs_reserve_extent ----find_free_extent (get bytenr of A) --btrfs_init_new_buffer (use bytenr of A) ----btrfs_find_create_tree_block ------alloc_extent_buffer --------find_extent_buffer (get A) 5. Eventually causing send process to access illegal memory. Thus extent_buffer_get can only prevent extent_buffer from being released, but it cannot prevent extent_buffer from being used by others. [FIX] So we fixed the problem by copy commit_root to avoid accessing illegal memory. CallTrace looks like this: ------------[ cut here ]------------ kernel BUG at fs/btrfs/ctree.c:1861! invalid opcode: 0000 [#1] SMP CPU: 6 PID: 24235 Comm: btrfs Tainted: P O 3.10.105 #23721 ffff88046652d680 ti: ffff88041b720000 task.ti: ffff88041b720000 RIP: 0010:[] read_node_slot+0x108/0x110 [btrfs] RSP: 0018:ffff88041b723b68 EFLAGS: 00010246 RAX: ffff88043ca6b000 RBX: ffff88041b723c50 RCX: ffff880000000000 RDX: 000000000000004c RSI: ffff880314b133f8 RDI: ffff880458b24000 RBP: 0000000000000000 R08: 0000000000000001 R09: ffff88041b723c66 R10: 0000000000000001 R11: 0000000000001000 R12: ffff8803f3e48890 R13: ffff8803f3e48880 R14: ffff880466351800 R15: 0000000000000001 FS: 00007f8c321dc8c0(0000) GS:ffff88047fcc0000(0000) CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 R2: 00007efd1006d000 CR3: 0000000213a24000 CR4: 00000000003407e0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 Stack: ffff88041b723c50 ffff8803f3e48880 ffff8803f3e48890 ffff8803f3e48880 ffff880466351800 0000000000000001 ffffffffa08dd9d7 ffff88041b723c50 ffff8803f3e48880 ffff88041b723c66 ffffffffa08dde85 a9ff88042d2c4400 Call Trace: [] ? tree_move_down.isra.33+0x27/0x50 [btrfs] [] ? tree_advance+0xb5/0xc0 [btrfs] [] ? btrfs_compare_trees+0x2d4/0x760 [btrfs] [] ? finish_inode_if_needed+0x870/0x870 [btrfs] [] ? btrfs_ioctl_send+0xeda/0x1050 [btrfs] [] ? btrfs_ioctl+0x1e3d/0x33f0 [btrfs] [] ? handle_pte_fault+0x373/0x990 [] ? atomic_notifier_call_chain+0x16/0x20 [] ? set_task_cpu+0xb6/0x1d0 [] ? handle_mm_fault+0x143/0x2a0 [] ? __do_page_fault+0x1d0/0x500 [] ? check_preempt_curr+0x57/0x90 [] ? do_vfs_ioctl+0x4aa/0x990 [] ? do_fork+0x113/0x3b0 [] ? trace_hardirqs_off_thunk+0x3a/0x6c [] ? SyS_ioctl+0x88/0xa0 [] ? system_call_fastpath+0x16/0x1b ---[ end trace 29576629ee80b2e1 ]--- Signed-off-by: Robbie Ko --- V2: fix commets fs/btrfs/ctree.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c index b88a79e..c9ce52f 100644 --- a/fs/btrfs/ctree.c +++ b/fs/btrfs/ctree.c @@ -5398,6 +5398,8 @@ int btrfs_compare_trees(struct btrfs_root *left_root, u64 right_blockptr; u64 left_gen; u64 right_gen; + struct extent_buffer *left_root_node = NULL; + struct extent_buffer *right_root_node = NULL; left_path = btrfs_alloc_path(); if (!left_path) { @@ -5416,6 +5418,20 @@ int btrfs_compare_trees(struct btrfs_root *left_root, goto out; } + left_root_node = alloc_dummy_extent_buffer(left_root->fs_info, 0); + if (!left_root_node) { + ret = -ENOMEM; + goto out; + } + extent_buffer_get(left_root_node); + + right_root_node = alloc_dummy_extent_buffer(left_root->fs_info, 0); + if (!right_root_node) { + ret = -ENOMEM; + goto out; + } + extent_buffer_get(right_root_node); + left_path->search_commit_root = 1; left_path->skip_locking = 1; right_path->search_commit_root = 1; @@ -5460,12 +5476,16 @@ int btrfs_compare_trees(struct btrfs_root *left_root, down_read(&fs_info->commit_root_sem); left_level = btrfs_header_level(left_root->commit_root); left_root_level = left_level; - left_path->nodes[left_level] = left_root->commit_root; + copy_extent_buffer_full(left_root_node, left_root->commit_root); + set_bit(EXTENT_BUFFER_UPTODATE, &left_root_node->bflags); + left_path->nodes[left_level] = left_root_node; extent_buffer_get(left_path->nodes[left_level]); right_level = btrfs_header_level(right_root->commit_root); right_root_level = right_level; - right_path->nodes[right_level] = right_root->commit_root; + copy_extent_buffer_full(right_root_node, right_root->commit_root); + set_bit(EXTENT_BUFFER_UPTODATE, &right_root_node->bflags); + right_path->nodes[right_level] = right_root_node; extent_buffer_get(right_path->nodes[right_level]); up_read(&fs_info->commit_root_sem); @@ -5613,6 +5633,8 @@ int btrfs_compare_trees(struct btrfs_root *left_root, out: btrfs_free_path(left_path); btrfs_free_path(right_path); + free_extent_buffer(left_root_node); + free_extent_buffer(right_root_node); kvfree(tmp_buf); return ret; }