• H
    jffs2: protect no-raw-node-ref check of inocache by erase_completion_lock · a7d48da7
    Hou Tao 提交于
    euler inclusion
    category: bugfix
    bugzilla: 47446
    CVE: NA
    --------------------------------------------------
    
    In jffs2_do_clear_inode(), we will check whether or not there is any
    jffs2_raw_node_ref associated with the current inocache. If there
    is no raw-node-ref, the inocache could be freed. And if there are
    still some jffs2_raw_node_ref linked in inocache->nodes, the inocache
    could not be freed and its free will be decided by
    jffs2_remove_node_refs_from_ino_list().
    
    However there is a race between jffs2_do_clear_inode() and
    jffs2_remove_node_refs_from_ino_list() as shown in the following
    scenario:
    
    CPU 0                   CPU 1
    in sys_unlink()         in jffs2_garbage_collect_pass()
    
    jffs2_do_unlink
      f->inocache->pino_nlink = 0
      set_nlink(inode, 0)
    
                            // contains all raw-node-refs of the unlinked inode
                            start GC a jeb
    
    iput_final
    jffs2_evict_inode
    jffs2_do_clear_inode
      acquire f->sem
        mark all refs as obsolete
    
                            GC complete
                            jeb is moved to erase_pending_list
                            jffs2_erase_pending_blocks
                              jffs2_free_jeb_node_refs
                                jffs2_remove_node_refs_from_ino_list
    
        f->inocache = INO_STATE_CHECKEDABSENT
    
                                  // no raw-node-ref is associated with the
                                  // inocache of the unlinked inode
                                  ic->nodes == (void *)ic && ic->pino_nlink == 0
                                    jffs2_del_ino_cache
    
        f->inodecache->nodes == f->nodes
          // double-free occurs
          jffs2_del_ino_cache
    
    Double-free of inocache will lead to all kinds of weired behaviours. The
    following BUG_ON is one case in which two active inodes are used the same
    inocache (the freed inocache is reused by a new inode, then the inocache
    is double-freed and reused by another new inode):
    
      jffs2: Raw node at 0x006c6000 wasn't in node lists for ino #662249
      ------------[ cut here ]------------
      kernel BUG at fs/jffs2/gc.c:645!
      invalid opcode: 0000 [#1] PREEMPT SMP
      Modules linked in: nandsim
      CPU: 0 PID: 15837 Comm: cp Not tainted 4.4.172 #1
      Hardware name: QEMU Standard PC (i440FX + PIIX, 1996)
      RIP: [<ffffffff816f1256>] jffs2_garbage_collect_live+0x1578/0x1593
      Call Trace:
       [<ffffffff8154b8aa>] jffs2_garbage_collect_pass+0xf6a/0x15d0
       [<ffffffff81541bbd>] jffs2_reserve_space+0x2bd/0x8a0
       [<ffffffff81546a62>] jffs2_do_create+0x52/0x480
       [<ffffffff8153c9f2>] jffs2_create+0xe2/0x2a0
       [<ffffffff8133bed7>] vfs_create+0xe7/0x220
       [<ffffffff81340ab4>] path_openat+0x11f4/0x1c00
       [<ffffffff81343635>] do_filp_open+0xa5/0x140
       [<ffffffff813288ed>] do_sys_open+0x19d/0x320
       [<ffffffff81328a96>] SyS_open+0x26/0x30
       [<ffffffff81c3f8f8>] entry_SYSCALL_64_fastpath+0x18/0x73
      ---[ end trace dd5c02f1653e8cac ]---
    
    Fix it by protecting no-raw-node-ref check by erase_completion_lock.
    And also need to move the call of jffs2_set_inocache_state() under
    erase_completion_lock, else the inocache may be leaked because
    jffs2_del_ino_cache() invoked by jffs2_remove_node_refs_from_ino_list()
    may find the state of inocache is still INO_STATE_CHECKING and will
    not free the inocache.
    
    Link: http://lists.infradead.org/pipermail/linux-mtd/2019-February/087764.htmlSigned-off-by: NHou Tao <houtao1@huawei.com>
    Reviewed-by: NWei Fang <fangwei1@huawei.com>
    Signed-off-by: Nzhangyi (F) <yi.zhang@huawei.com>
    [cherry-pick from hulk-4.4]
    Signed-off-by: Nyangerkun <yangerkun@huawei.com>
    Reviewed-by: NHou Tao <houtao1@huawei.com>
    Signed-off-by: NZheng Zengkai <zhengzengkai@huawei.com>
    a7d48da7
readinode.c 43.4 KB