Re: [PATCH] ntfs: rewrite EA stream before updating metadata

Hyunchul Lee <[email protected]> Fri, 17 Jul 2026 07:27:22 +0900
Newsgroups dev.linux.lists.ntfs,org.kernel.vger.linux-fsdevel
Message-ID <CANFS6bZ7oASEgR57GkQNvos3cSm9G5sLWkVvET=OPUEXruxjcA@mail.gmail.com>
2026=EB=85=84 7=EC=9B=94 16=EC=9D=BC (=EB=AA=A9) =EC=98=A4=EC=A0=84 11:47, =
Namjae Jeon <[email protected]>=EB=8B=98=EC=9D=B4 =EC=9E=91=EC=84=B1:
>
> 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]>

Looks good to me.

Reviewed-by: Hyunchul Lee <[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 cha=
r *name, size_t name_len,
>         struct ea_attr *p_ea;
>         u32 ea_info_qsize =3D 0;
>         char *ea_buf =3D NULL;
> +       char *new_ea_buf;
> +       char *old_ea_buf =3D NULL;
> +       struct ea_information old_ea_info;
>         size_t new_ea_size =3D 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 ch=
ar *name, size_t name_len,
>                         err =3D -EEXIST;
>                         goto out;
>                 }
> +               if ((flags & XATTR_REPLACE) && !val_size) {
> +                       old_ea_info =3D *p_ea_info;
> +                       old_ea_buf =3D kvmemdup(ea_buf, all_ea_size, GFP_=
NOFS);
> +                       if (!old_ea_buf) {
> +                               err =3D -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 c=
har *name, size_t name_len,
>                                 goto out;
>
>                         err =3D ntfs_attr_remove(ni, AT_EA_INFORMATION, A=
T_UNNAMED, 0);
> +                       if (err) {
> +                               /* Restore the original $EA if $EA_INFORM=
ATION removal failed. */
> +                               ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, o=
ld_ea_buf,
> +                                             all_ea_size);
> +                               ea_info_qsize =3D le32_to_cpu(old_ea_info=
.ea_query_length);
> +                       }
>                         goto out;
>                 }
>
> -               err =3D ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea=
_info, 0,
> -                               sizeof(struct ea_information), false);
> -               if (err)
> -                       goto out;
> -
> -               err =3D 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 =3D ntfs_write_ea(ni, AT_EA, ea_buf, 0, ea_in=
fo_qsize,
> +                                       true);
> +                       if (err) {
> +                               ntfs_write_ea(ni, AT_EA, old_ea_buf, 0,
> +                                             all_ea_size, false);
> +                               goto out;
> +                       }
> +
> +                       err =3D ntfs_write_ea(ni, AT_EA_INFORMATION, (cha=
r *)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 c=
har *name, size_t name_len,
>                         goto out;
>                 }
>         }
> -       kvfree(ea_buf);
> -
>  alloc_new_ea:
> -       ea_buf =3D kzalloc(new_ea_size, GFP_NOFS);
> -       if (!ea_buf) {
> +       new_ea_buf =3D kvzalloc(ea_info_qsize + new_ea_size, GFP_NOFS);
> +       if (!new_ea_buf) {
>                 err =3D -ENOMEM;
>                 goto out;
>         }
> +       if (ea_info_qsize)
> +               memcpy(new_ea_buf, ea_buf, ea_info_qsize);
> +       kvfree(ea_buf);
> +       ea_buf =3D new_ea_buf;
> +       p_ea =3D (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 =3D (struct ea_attr *)ea_buf;
>         memcpy(p_ea->ea_name, name, name_len);
>         p_ea->ea_name_length =3D name_len;
>         p_ea->ea_name[name_len] =3D 0;
> @@ -344,13 +370,13 @@ static int ntfs_set_ea(struct inode *inode, const c=
har *name, size_t name_len,
>          * no EA or EA_INFORMATION : add them
>          */
>         if (!ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)) {
> -               err =3D ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, (char *)p=
_ea,
> -                               new_ea_size);
> +               err =3D ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, ea_buf,
> +                               ea_info_qsize + new_ea_size);
>                 if (err)
>                         goto out;
>         } else {
> -               err =3D ntfs_write_ea(ni, AT_EA, (char *)p_ea, ea_info_qs=
ize,
> -                               new_ea_size, false);
> +               err =3D 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 cha=
r *name, size_t name_len,
>                 NInoClearHasEA(ni);
>
>         kvfree(ea_buf);
> +       kvfree(old_ea_buf);
>         kvfree(p_ea_info);
>
>         return err;
> --
> 2.25.1
>


--=20
Thanks,
Hyunchul