[PATCH] ntfs: rewrite EA stream before updating metadata
Namjae Jeon <[email protected]> Thu, 16 Jul 2026 11:46:37 +0900
| Newsgroups | dev.linux.lists.ntfs,org.kernel.vger.linux-fsdevel |
|---|---|
| Message-ID | <[email protected]> |
Updating an EA removes the old record and appends its replacement.
Build the complete $EA stream in memory and rewrite it from offset zero,
rather than committing a compacted stream followed by a separate append.
generic/642 shows that the append path can leave an invalid record layout
on disk, including when a new EA entry is added.
When removing an EA, write the compacted stream before updating
$EA_INFORMATION and restore the original pair if the metadata update
fails.
When the final EA entry is removed the $EA/$EA_INFORMATION pair is torn
down. If removing $EA_INFORMATION fails after $EA has already been
removed, the original $EA is restored so the two attributes stay
consistent.
Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations")
Signed-off-by: Namjae Jeon <[email protected]>
---
fs/ntfs/ea.c | 65 +++++++++++++++++++++++++++++++++++++---------------
1 file changed, 46 insertions(+), 19 deletions(-)
diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c
index 88bfd6560692..d0044c61152a 100644
--- a/fs/ntfs/ea.c
+++ b/fs/ntfs/ea.c
@@ -196,6 +196,9 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
struct ea_attr *p_ea;
u32 ea_info_qsize = 0;
char *ea_buf = NULL;
+ char *new_ea_buf;
+ char *old_ea_buf = NULL;
+ struct ea_information old_ea_info;
size_t new_ea_size = ALIGN(struct_size(p_ea, ea_name, 1 + name_len + val_size), 4);
s64 ea_off, ea_info_size, all_ea_size, ea_size;
@@ -249,6 +252,14 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
err = -EEXIST;
goto out;
}
+ if ((flags & XATTR_REPLACE) && !val_size) {
+ old_ea_info = *p_ea_info;
+ old_ea_buf = kvmemdup(ea_buf, all_ea_size, GFP_NOFS);
+ if (!old_ea_buf) {
+ err = -ENOMEM;
+ goto out;
+ }
+ }
/* Check the final $EA size before removing the old entry. */
if (val_size &&
@@ -281,20 +292,33 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
goto out;
err = ntfs_attr_remove(ni, AT_EA_INFORMATION, AT_UNNAMED, 0);
+ if (err) {
+ /* Restore the original $EA if $EA_INFORMATION removal failed. */
+ ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, old_ea_buf,
+ all_ea_size);
+ ea_info_qsize = le32_to_cpu(old_ea_info.ea_query_length);
+ }
goto out;
}
- err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, 0,
- sizeof(struct ea_information), false);
- if (err)
- goto out;
-
- err = ntfs_write_ea(ni, AT_EA, ea_buf, 0, ea_info_qsize, true);
- if (err)
- goto out;
-
if ((flags & XATTR_REPLACE) && !val_size) {
- /* Remove xattr. */
+ err = ntfs_write_ea(ni, AT_EA, ea_buf, 0, ea_info_qsize,
+ true);
+ if (err) {
+ ntfs_write_ea(ni, AT_EA, old_ea_buf, 0,
+ all_ea_size, false);
+ goto out;
+ }
+
+ err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info,
+ 0, sizeof(struct ea_information), false);
+ if (err) {
+ ntfs_write_ea(ni, AT_EA, old_ea_buf, 0,
+ all_ea_size, false);
+ ntfs_write_ea(ni, AT_EA_INFORMATION,
+ (char *)&old_ea_info, 0,
+ sizeof(old_ea_info), false);
+ }
goto out;
}
} else {
@@ -309,21 +333,23 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
goto out;
}
}
- kvfree(ea_buf);
-
alloc_new_ea:
- ea_buf = kzalloc(new_ea_size, GFP_NOFS);
- if (!ea_buf) {
+ new_ea_buf = kvzalloc(ea_info_qsize + new_ea_size, GFP_NOFS);
+ if (!new_ea_buf) {
err = -ENOMEM;
goto out;
}
+ if (ea_info_qsize)
+ memcpy(new_ea_buf, ea_buf, ea_info_qsize);
+ kvfree(ea_buf);
+ ea_buf = new_ea_buf;
+ p_ea = (struct ea_attr *)(ea_buf + ea_info_qsize);
/*
* EA and REPARSE_POINT compatibility not checked any more,
* required by Windows 10, but having both may lead to
* problems with earlier versions.
*/
- p_ea = (struct ea_attr *)ea_buf;
memcpy(p_ea->ea_name, name, name_len);
p_ea->ea_name_length = name_len;
p_ea->ea_name[name_len] = 0;
@@ -344,13 +370,13 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
* no EA or EA_INFORMATION : add them
*/
if (!ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)) {
- err = ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, (char *)p_ea,
- new_ea_size);
+ err = ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, ea_buf,
+ ea_info_qsize + new_ea_size);
if (err)
goto out;
} else {
- err = ntfs_write_ea(ni, AT_EA, (char *)p_ea, ea_info_qsize,
- new_ea_size, false);
+ err = ntfs_write_ea(ni, AT_EA, ea_buf, 0,
+ ea_info_qsize + new_ea_size, true);
if (err)
goto out;
}
@@ -370,6 +396,7 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
NInoClearHasEA(ni);
kvfree(ea_buf);
+ kvfree(old_ea_buf);
kvfree(p_ea_info);
return err;
--
2.25.1