[PATCH v5 4/4] ntfs: protect mapping-pairs attr-list updates with attr_list_persist_lock
Hyunchul Lee <[email protected]>
| Newsgroups | dev.linux.lists.ntfs,org.kernel.vger.stable |
|---|---|
| Message-ID | <[email protected]> |
ntfs_attr_update_mapping_pairs() updates several ALEs while rebuilding a fragmented attribute's mapping pairs and persists the attribute list once after the loop. Each ALE update is covered by attr_list_lock, but the overall publish/persist sequence is not serialized against concurrent ntfs_attrlist_entry_add()/rm() on the same base inode. Acquire attr_list_persist_lock once after resolving base_ni whenever the call can touch the attribute list, and hold it across every ALE update and the deferred ntfs_attrlist_update() call. 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 | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- fs/ntfs/inode.c | 46 ++++++++++++++++++++++++++++++++++++++++---- fs/ntfs/inode.h | 1 + 3 files changed, 97 insertions(+), 8 deletions(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index f45f0289e3d3..2eac3493a43c 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -3718,6 +3718,7 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra) base_ni = ctx->base_ntfs_ino; else base_ni = ctx->ntfs_ino; + lockdep_assert_held(&base_ni->attr_list_persist_lock); sb = ctx->ntfs_ino->vol->sb; if (!NInoAttrList(base_ni)) { @@ -3726,7 +3727,7 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra) return -EINVAL; } - err = ntfs_inode_attach_all_extents(ctx->ntfs_ino); + err = ntfs_inode_attach_all_extents_locked(ctx->ntfs_ino); if (err) { ntfs_error(sb, "Couldn't attach extents, inode=%llu", (unsigned long long)base_ni->mft_no); @@ -3790,7 +3791,8 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra) * update allocated and compressed size. */ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, - struct mft_record *m, struct ntfs_attr_search_ctx *ctx) + struct mft_record *m, struct ntfs_attr_search_ctx *ctx, + bool *attrlist_locked) { int sparse, err = 0; struct ntfs_inode *base_ni; @@ -3827,6 +3829,19 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni, !(le32_to_cpu(m->bytes_allocated) - le32_to_cpu(m->bytes_in_use))) { if (!NInoAttrList(base_ni)) { + /* + * ntfs_inode_add_attrlist() acquires + * attr_list_persist_lock itself, so drop it + * first if our caller took it for us. Nothing + * is in flight that needs protecting: without + * an attribute list no ALE has been touched + * yet. The -EAGAIN below makes the caller + * restart and re-acquire the lock. + */ + if (*attrlist_locked) { + mutex_unlock(&base_ni->attr_list_persist_lock); + *attrlist_locked = false; + } err = ntfs_inode_add_attrlist(base_ni); if (err) goto out; @@ -3953,7 +3968,7 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) struct attr_record *a; s64 stop_vcn; int err = 0, mp_size, cur_max_mp_size, exp_max_mp_size; - bool finished_build, attrlist_changed = false; + bool finished_build, attrlist_changed = false, attrlist_locked = false; bool first_updated = false; struct super_block *sb; struct runlist_element *start_rl; @@ -3978,9 +3993,16 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) else base_ni = ni; + if (ni->type != AT_ATTRIBUTE_LIST) { + mutex_lock(&base_ni->attr_list_persist_lock); + attrlist_locked = true; + } + ctx = ntfs_attr_get_search_ctx(base_ni, NULL); if (!ctx) { ntfs_error(sb, "%s: Failed to get search context", __func__); + if (attrlist_locked) + mutex_unlock(&base_ni->attr_list_persist_lock); return -ENOMEM; } @@ -4051,9 +4073,13 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) continue; } - err = ntfs_attr_update_meta(a, ni, m, ctx); + err = ntfs_attr_update_meta(a, ni, m, ctx, &attrlist_locked); if (err < 0) { if (err == -EAGAIN) { + if (attrlist_locked) { + mutex_unlock(&base_ni->attr_list_persist_lock); + attrlist_locked = false; + } ntfs_attr_put_search_ctx(ctx); goto retry; } @@ -4090,6 +4116,10 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) * attributes and try again. */ if (ni->type == AT_ATTRIBUTE_LIST) { + if (WARN_ON_ONCE(attrlist_locked)) { + mutex_unlock(&base_ni->attr_list_persist_lock); + attrlist_locked = false; + } ntfs_attr_put_search_ctx(ctx); if (ntfs_inode_free_space(base_ni, mp_size - cur_max_mp_size)) { @@ -4110,6 +4140,18 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) /* Add attribute list if it isn't present, and retry. */ if (!NInoAttrList(base_ni)) { + /* + * We hold attr_list_persist_lock even when the + * inode has no attribute list yet, since one + * can appear under us. ntfs_inode_add_attrlist() + * takes the same lock, so drop it here; no ALE + * has been touched, so there is no transaction + * to break, and the retry re-acquires it. + */ + if (attrlist_locked) { + mutex_unlock(&base_ni->attr_list_persist_lock); + attrlist_locked = false; + } ntfs_attr_put_search_ctx(ctx); if (ntfs_inode_add_attrlist(base_ni)) { ntfs_error(sb, "Can not add attrlist"); @@ -4226,6 +4268,12 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) if (err) goto put_err_out; } + if (attrlist_locked) { + mutex_unlock(&base_ni->attr_list_persist_lock); + attrlist_locked = false; + } + if (attrlist_changed && err) + goto put_err_out; /* Deallocate not used attribute extents and return with success. */ if (finished_build) { @@ -4352,6 +4400,8 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn) return 0; put_err_out: + if (attrlist_locked) + mutex_unlock(&base_ni->attr_list_persist_lock); if (ctx) ntfs_attr_put_search_ctx(ctx); return err; diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index f62d72857558..a52762b4659f 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -2921,6 +2921,7 @@ static struct ntfs_inode *ntfs_extent_inode_open(struct ntfs_inode *base_ni, if (!base_ni) return NULL; + lockdep_assert_held(&base_ni->extent_lock); sb = base_ni->vol->sb; ntfs_debug("Opening extent inode %llu (base mft record %llu).\n", @@ -2988,15 +2989,20 @@ static struct ntfs_inode *ntfs_extent_inode_open(struct ntfs_inode *base_ni, } /* - * ntfs_inode_attach_all_extents - attach all extents for target inode + * ntfs_inode_attach_all_extents_locked - attach all extents for target inode * @ni: opened ntfs inode for which perform attach * + * Caller must hold the base inode's attr_list_persist_lock. The attribute + * list buffer is replaced and freed by transactions serialized by this lock, + * so walking it without the lock can dereference a stale buffer. + * * Return 0 on success and error. */ -int ntfs_inode_attach_all_extents(struct ntfs_inode *ni) +int ntfs_inode_attach_all_extents_locked(struct ntfs_inode *ni) { struct attr_list_entry *ale; u64 prev_attached = 0; + int err = 0; if (!ni) { ntfs_debug("Invalid arguments.\n"); @@ -3005,6 +3011,7 @@ int ntfs_inode_attach_all_extents(struct ntfs_inode *ni) if (NInoAttr(ni)) ni = ni->ext.base_ntfs_ino; + lockdep_assert_held(&ni->attr_list_persist_lock); ntfs_debug("Entering for inode 0x%llx.\n", ni->mft_no); @@ -3018,19 +3025,50 @@ int ntfs_inode_attach_all_extents(struct ntfs_inode *ni) } /* Walk through attribute list and attach all extents. */ + mutex_lock(&ni->extent_lock); ale = (struct attr_list_entry *)ni->attr_list; while ((u8 *)ale < ni->attr_list + ni->attr_list_size) { if (ni->mft_no != MREF_LE(ale->mft_reference) && prev_attached != MREF_LE(ale->mft_reference)) { if (!ntfs_extent_inode_open(ni, ale->mft_reference)) { ntfs_debug("Couldn't attach extent inode.\n"); - return -1; + err = -1; + break; } prev_attached = MREF_LE(ale->mft_reference); } ale = (struct attr_list_entry *)((u8 *)ale + le16_to_cpu(ale->length)); } - return 0; + mutex_unlock(&ni->extent_lock); + return err; +} + +/* + * ntfs_inode_attach_all_extents - attach all extents for target inode + * @ni: opened ntfs inode for which perform attach + * + * Return 0 on success and error. + */ +int ntfs_inode_attach_all_extents(struct ntfs_inode *ni) +{ + struct ntfs_inode *base_ni; + int err; + + if (!ni) { + ntfs_debug("Invalid arguments.\n"); + return -EINVAL; + } + + if (NInoAttr(ni)) + base_ni = ni->ext.base_ntfs_ino; + else + base_ni = ni; + + mutex_lock(&base_ni->attr_list_persist_lock); + err = ntfs_inode_attach_all_extents_locked(base_ni); + mutex_unlock(&base_ni->attr_list_persist_lock); + + return err; } /* diff --git a/fs/ntfs/inode.h b/fs/ntfs/inode.h index a6c5b15d648d..a02ef1e59044 100644 --- a/fs/ntfs/inode.h +++ b/fs/ntfs/inode.h @@ -346,6 +346,7 @@ int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path, int ntfs_get_block_mft_record(struct ntfs_inode *mft_ni, struct ntfs_inode *ni); int __ntfs_write_inode(struct inode *vi, int sync); int ntfs_inode_attach_all_extents(struct ntfs_inode *ni); +int ntfs_inode_attach_all_extents_locked(struct ntfs_inode *ni); int ntfs_inode_add_attrlist(struct ntfs_inode *ni); void ntfs_destroy_ext_inode(struct ntfs_inode *ni); int ntfs_inode_free_space(struct ntfs_inode *ni, int size); -- 2.43.0