Hou Tao (4): jffs2: protect no-raw-node-ref check of inocache by erase_completion_lock jffs2: handle INO_STATE_CLEARING in jffs2_do_read_inode() jffs2: make the overwritten xattr invisible after remount jffs2: reset pino_nlink to 0 when inode creation failed
fs/jffs2/dir.c | 28 ++++++++++++++++++---- fs/jffs2/nodelist.c | 7 ++++++ fs/jffs2/readinode.c | 11 ++++++++- fs/jffs2/xattr.c | 55 +++++++++++++++++++++++++++++++++++++++----- 4 files changed, 90 insertions(+), 11 deletions(-)
From: Hou Tao houtao1@huawei.com
euler inclusion category: bugfix bugzilla: https://gitee.com/openeuler/kernel/issues/I8TYET 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.html Signed-off-by: Hou Tao houtao1@huawei.com Signed-off-by: ZhaoLong Wang wangzhaolong1@huawei.com --- fs/jffs2/nodelist.c | 7 +++++++ fs/jffs2/readinode.c | 10 +++++++++- 2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/fs/jffs2/nodelist.c b/fs/jffs2/nodelist.c index b86c78d178c6..c3b0d56e7007 100644 --- a/fs/jffs2/nodelist.c +++ b/fs/jffs2/nodelist.c @@ -469,6 +469,13 @@ void jffs2_del_ino_cache(struct jffs2_sb_info *c, struct jffs2_inode_cache *old) while ((*prev) && (*prev)->ino < old->ino) { prev = &(*prev)->next; } + + /* + * It's possible that we can not find the inocache in + * hash table because it had been removed by + * jffs2_remove_node_refs_from_ino_list(), but it's still not freed, + * so we need go forward and free it. + */ if ((*prev) == old) { *prev = old->next; } diff --git a/fs/jffs2/readinode.c b/fs/jffs2/readinode.c index 03b4f99614be..201622e2244f 100644 --- a/fs/jffs2/readinode.c +++ b/fs/jffs2/readinode.c @@ -1438,8 +1438,16 @@ void jffs2_do_clear_inode(struct jffs2_sb_info *c, struct jffs2_inode_info *f) }
if (f->inocache && f->inocache->state != INO_STATE_CHECKING) { - jffs2_set_inocache_state(c, f->inocache, INO_STATE_CHECKEDABSENT); + bool need_del = false; + + spin_lock(&c->erase_completion_lock); if (f->inocache->nodes == (void *)f->inocache) + need_del = true; + jffs2_set_inocache_state(c, f->inocache, + INO_STATE_CHECKEDABSENT); + spin_unlock(&c->erase_completion_lock); + + if (need_del) jffs2_del_ino_cache(c, f->inocache); }
From: Hou Tao houtao1@huawei.com
hulk inclusion category: bugfix bugzilla: https://gitee.com/openeuler/kernel/issues/I8TYET CVE: NA
--------------------------
For inode that fails to be created midway, GC procedure may try to GC its dnode, and in the following case BUG() will be triggered:
CPU 0 CPU 1 in jffs2_do_create() in jffs2_garbage_collect_pass()
jffs2_write_dnode succeed // for dirent jffs2_reserve_space fail
inum = ic->ino nlink = ic->pino_nlink (> 0)
iget_failed make_bad_inode remove_inode_hash iput jffs2_evict_inode jffs2_do_clear_inode jffs2_set_inocache_state(INO_STATE_CLEARING)
jffs2_gc_fetch_inode jffs2_iget // a new inode is created because // the old inode had been unhashed iget_locked jffs2_do_read_inode jffs2_get_ino_cache // assert BUG() f->inocache->state = INO_STATE_CLEARING
Fix it by waiting for its state changes to INO_STATE_CHECKEDABSENT.
Link: http://lists.infradead.org/pipermail/linux-mtd/2019-February/087762.html Signed-off-by: Hou Tao houtao1@huawei.com Signed-off-by: ZhaoLong Wang wangzhaolong1@huawei.com --- fs/jffs2/readinode.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/fs/jffs2/readinode.c b/fs/jffs2/readinode.c index 201622e2244f..00145ae41356 100644 --- a/fs/jffs2/readinode.c +++ b/fs/jffs2/readinode.c @@ -1344,6 +1344,7 @@ int jffs2_do_read_inode(struct jffs2_sb_info *c, struct jffs2_inode_info *f,
case INO_STATE_CHECKING: case INO_STATE_GC: + case INO_STATE_CLEARING: /* If it's in either of these states, we need to wait for whoever's got it to finish and put it back. */
From: Hou Tao houtao1@huawei.com
euler inclusion category: bugfix bugzilla: https://gitee.com/openeuler/kernel/issues/I8TYET CVE: NA
-------------------------------------------------
For xattr modification, we do not write a new jffs2_raw_xref with delete marker into flash, so if a xattr is modified then removed, and the old xref & xdatum are not erased by GC, after reboot or remount, the new xattr xref will be dead but the old xattr xref will be alive, and we will get the overwritten xattr instead of non-existent error when reading the removed xattr.
Fix it by writing the deletion mark for xattr overwrite.
Fixes: 8a13695cbe4e ("[JFFS2][XATTR] rid unnecessary writing of delete marker.") Signed-off-by: Hou Tao houtao1@huawei.com Signed-off-by: ZhaoLong Wang wangzhaolong1@huawei.com --- fs/jffs2/xattr.c | 55 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 6 deletions(-)
diff --git a/fs/jffs2/xattr.c b/fs/jffs2/xattr.c index 3b6bdc9a49e1..1e65b7b06c22 100644 --- a/fs/jffs2/xattr.c +++ b/fs/jffs2/xattr.c @@ -573,6 +573,15 @@ static struct jffs2_xattr_ref *create_xattr_ref(struct jffs2_sb_info *c, struct return ref; /* success */ }
+static void move_xattr_ref_to_dead_list(struct jffs2_sb_info *c, + struct jffs2_xattr_ref *ref) +{ + spin_lock(&c->erase_completion_lock); + ref->next = c->xref_dead_list; + c->xref_dead_list = ref; + spin_unlock(&c->erase_completion_lock); +} + static void delete_xattr_ref(struct jffs2_sb_info *c, struct jffs2_xattr_ref *ref) { /* must be called under down_write(xattr_sem) */ @@ -582,10 +591,7 @@ static void delete_xattr_ref(struct jffs2_sb_info *c, struct jffs2_xattr_ref *re ref->xseqno |= XREF_DELETE_MARKER; ref->ino = ref->ic->ino; ref->xid = ref->xd->xid; - spin_lock(&c->erase_completion_lock); - ref->next = c->xref_dead_list; - c->xref_dead_list = ref; - spin_unlock(&c->erase_completion_lock); + move_xattr_ref_to_dead_list(c, ref);
dbg_xattr("xref(ino=%u, xid=%u, xseqno=%u) was removed.\n", ref->ino, ref->xid, ref->xseqno); @@ -1094,6 +1100,40 @@ int do_jffs2_getxattr(struct inode *inode, int xprefix, const char *xname, return rc; }
+static void do_jffs2_delete_xattr_ref(struct jffs2_sb_info *c, + struct jffs2_xattr_ref *ref) +{ + uint32_t request, length; + int err; + struct jffs2_xattr_datum *xd; + + request = PAD(sizeof(struct jffs2_raw_xref)); + err = jffs2_reserve_space(c, request, &length, + ALLOC_NORMAL, JFFS2_SUMMARY_XREF_SIZE); + down_write(&c->xattr_sem); + if (err) { + JFFS2_WARNING("jffs2_reserve_space()=%d, request=%u\n", + err, request); + delete_xattr_ref(c, ref); + up_write(&c->xattr_sem); + return; + } + + xd = ref->xd; + ref->ino = ref->ic->ino; + ref->xid = xd->xid; + ref->xseqno |= XREF_DELETE_MARKER; + save_xattr_ref(c, ref); + + move_xattr_ref_to_dead_list(c, ref); + dbg_xattr("xref(ino=%u, xid=%u, xseqno=%u) was removed.\n", + ref->ino, ref->xid, ref->xseqno); + unrefer_xattr_datum(c, xd); + + up_write(&c->xattr_sem); + jffs2_complete_reservation(c); +} + int do_jffs2_setxattr(struct inode *inode, int xprefix, const char *xname, const char *buffer, size_t size, int flags) { @@ -1101,7 +1141,7 @@ int do_jffs2_setxattr(struct inode *inode, int xprefix, const char *xname, struct jffs2_sb_info *c = JFFS2_SB_INFO(inode->i_sb); struct jffs2_inode_cache *ic = f->inocache; struct jffs2_xattr_datum *xd; - struct jffs2_xattr_ref *ref, *newref, **pref; + struct jffs2_xattr_ref *ref, *newref, *oldref, **pref; uint32_t length, request; int rc;
@@ -1117,6 +1157,7 @@ int do_jffs2_setxattr(struct inode *inode, int xprefix, const char *xname, return rc; }
+ oldref = NULL; /* Find existing xattr */ down_write(&c->xattr_sem); retry: @@ -1200,11 +1241,13 @@ int do_jffs2_setxattr(struct inode *inode, int xprefix, const char *xname, rc = PTR_ERR(newref); unrefer_xattr_datum(c, xd); } else if (ref) { - delete_xattr_ref(c, ref); + oldref = ref; } out: up_write(&c->xattr_sem); jffs2_complete_reservation(c); + if (oldref) + do_jffs2_delete_xattr_ref(c, oldref); return rc; }
From: Hou Tao houtao1@huawei.com
hulk inclusion category: bugfix bugzilla: https://gitee.com/openeuler/kernel/issues/I8TYET CVE: NA
-------------------------------------------------
So jffs2_do_clear_inode() could mark all flash nodes used by the inode as obsolete and GC procedure will reclaim these flash nodes, else these flash spaces will not be reclaimable forever.
Link: http://lists.infradead.org/pipermail/linux-mtd/2019-February/087763.html Signed-off-by: Hou Tao houtao1@huawei.com Signed-off-by: ZhaoLong Wang wangzhaolong1@huawei.com --- fs/jffs2/dir.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-)
diff --git a/fs/jffs2/dir.c b/fs/jffs2/dir.c index 091ab0eaabbe..19d768ef2217 100644 --- a/fs/jffs2/dir.c +++ b/fs/jffs2/dir.c @@ -159,6 +159,26 @@ static int jffs2_readdir(struct file *file, struct dir_context *ctx)
/***********************************************************************/
+static void jffs2_iget_failed(struct jffs2_sb_info *c, struct inode *inode) +{ + struct jffs2_inode_info *f = JFFS2_INODE_INFO(inode); + + /* + * Reset pino_nlink to zero, so jffs2_do_clear_inode() will mark + * all flash nodes used by the inode as obsolete and GC procedure + * will reclaim these flash nodes, else these flash spaces will be + * unreclaimable forever. + * + * Update pino_nlink under inocache_lock, because no proceses could + * get the inode due to I_NEW flag, and only GC procedure may try to + * read pino_nlink under inocache_lock. + */ + spin_lock(&c->inocache_lock); + f->inocache->pino_nlink = 0; + spin_unlock(&c->inocache_lock); + + iget_failed(inode); +}
static int jffs2_create(struct mnt_idmap *idmap, struct inode *dir_i, struct dentry *dentry, umode_t mode, bool excl) @@ -217,7 +237,7 @@ static int jffs2_create(struct mnt_idmap *idmap, struct inode *dir_i, return 0;
fail: - iget_failed(inode); + jffs2_iget_failed(c, inode); jffs2_free_raw_inode(ri); return ret; } @@ -439,7 +459,7 @@ static int jffs2_symlink (struct mnt_idmap *idmap, struct inode *dir_i, return 0;
fail: - iget_failed(inode); + jffs2_iget_failed(c, inode); return ret; }
@@ -585,7 +605,7 @@ static int jffs2_mkdir (struct mnt_idmap *idmap, struct inode *dir_i, return 0;
fail: - iget_failed(inode); + jffs2_iget_failed(c, inode); return ret; }
@@ -762,7 +782,7 @@ static int jffs2_mknod (struct mnt_idmap *idmap, struct inode *dir_i, return 0;
fail: - iget_failed(inode); + jffs2_iget_failed(c, inode); return ret; }
反馈: 您发送到kernel@openeuler.org的补丁/补丁集,已成功转换为PR! PR链接地址: https://gitee.com/openeuler/kernel/pulls/3815 邮件列表地址:https://mailweb.openeuler.org/hyperkitty/list/kernel@openeuler.org/message/7...
FeedBack: The patch(es) which you have sent to kernel@openeuler.org mailing list has been converted to a pull request successfully! Pull request link: https://gitee.com/openeuler/kernel/pulls/3815 Mailing list address: https://mailweb.openeuler.org/hyperkitty/list/kernel@openeuler.org/message/7...