[PATCH v2 3/4] ntfs: protect attribute-list creation/teardown with attr_list_persist_lock
Hyunchul Lee <[email protected]> Tue, 28 Jul 2026 09:30:50 +0900
| Newsgroups | dev.linux.lists.ntfs,org.kernel.vger.stable |
|---|---|
| Message-ID | <[email protected]> |
ntfs_attr_record_rm() and ntfs_inode_add_attrlist() update the in-memory attribute list without any lock, so a concurrent reader can observe an inconsistent trio. Teardown can also free a buffer that a concurrent ntfs_attrlist_entry_add()/rm() transaction still owns while it sleeps in ntfs_attrlist_update(). Protect both functions with attr_list_persist_lock for their whole publish/persist or teardown sequence, and with attr_list_lock (write) around the actual buffer pointer/size/gen updates. Reported-by: Cen Zhang <[email protected]> Link: https://lore.kernel.org/all/[email protected]/ Fixes: 495e90fa3348 ("ntfs: update attrib operations") Cc: [email protected] Tested-by: Cen Zhang <[email protected]> Signed-off-by: Hyunchul Lee <[email protected]> --- fs/ntfs/attrib.c | 11 +++++++++++ fs/ntfs/inode.c | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 378e655c7028..00ff223d9c21 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -2816,10 +2816,21 @@ int ntfs_attr_record_rm(struct ntfs_attr_search_ctx *ctx) /* Post $ATTRIBUTE_LIST delete setup. */ if (type == AT_ATTRIBUTE_LIST) { + /* + * attr_list_persist_lock serializes this in-memory attr_list + * teardown against a concurrent ntfs_attrlist_entry_add()/rm() + * transaction on the same inode. + */ + mutex_lock(&base_ni->attr_list_persist_lock); + down_write(&base_ni->attr_list_lock); if (NInoAttrList(base_ni) && base_ni->attr_list) kvfree(base_ni->attr_list); base_ni->attr_list = NULL; + base_ni->attr_list_size = 0; + base_ni->attr_list_gen++; NInoClearAttrList(base_ni); + up_write(&base_ni->attr_list_lock); + mutex_unlock(&base_ni->attr_list_persist_lock); } /* Free MFT record, if it doesn't contain attributes. */ diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index c568ddf851f4..4ac63bbd47a6 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -3049,6 +3049,7 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni) struct attr_list_entry *ale = NULL; struct mft_record *ni_mrec; u32 attr_al_len; + bool attrlist_locked = false; if (!ni) return -EINVAL; @@ -3124,9 +3125,14 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni) } /* Set in-memory attribute list. */ + mutex_lock(&ni->attr_list_persist_lock); + attrlist_locked = true; + down_write(&ni->attr_list_lock); ni->attr_list = al; ni->attr_list_size = al_len; + ni->attr_list_gen++; NInoSetAttrList(ni); + up_write(&ni->attr_list_lock); attr_al_len = offsetof(struct attr_record, data.resident.reserved) + 1 + ((al_len + 7) & ~7); @@ -3153,14 +3159,21 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni) if (err < 0) goto remove_attrlist_record; + mutex_unlock(&ni->attr_list_persist_lock); + attrlist_locked = false; ntfs_attr_put_search_ctx(ctx); unmap_mft_record(ni); return 0; remove_attrlist_record: - /* Prevent ntfs_attr_recorm_rm from freeing attribute list. */ + down_write(&ni->attr_list_lock); ni->attr_list = NULL; + ni->attr_list_gen++; NInoClearAttrList(ni); + up_write(&ni->attr_list_lock); + mutex_unlock(&ni->attr_list_persist_lock); + attrlist_locked = false; + /* Remove $ATTRIBUTE_LIST record. */ ntfs_attr_reinit_search_ctx(ctx); if (!ntfs_attr_lookup(AT_ATTRIBUTE_LIST, NULL, 0, @@ -3171,10 +3184,20 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni) ntfs_error(ni->vol->sb, "Rollback failed to find attrlist"); } + /* + * Without this lock, a concurrent ntfs_attrlist_entry_add()/rm() + * could replace or free @al out from under this loop. + */ + mutex_lock(&ni->attr_list_persist_lock); + attrlist_locked = true; + /* Setup back in-memory runlist. */ + down_write(&ni->attr_list_lock); ni->attr_list = al; ni->attr_list_size = al_len; + ni->attr_list_gen++; NInoSetAttrList(ni); + up_write(&ni->attr_list_lock); rollback: /* * Scan attribute list for attributes that placed not in the base MFT @@ -3201,10 +3224,19 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni) } /* Remove in-memory attribute list. */ + down_write(&ni->attr_list_lock); ni->attr_list = NULL; ni->attr_list_size = 0; + ni->attr_list_gen++; NInoClearAttrList(ni); NInoClearAttrListDirty(ni); + up_write(&ni->attr_list_lock); + + if (attrlist_locked) { + mutex_unlock(&ni->attr_list_persist_lock); + attrlist_locked = false; + } + put_err_out: ntfs_attr_put_search_ctx(ctx); err_out: -- 2.43.0