mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
To: Yuejie Shi <syjcnss@gmail.com>
Cc: <ntfs3@lists.linux.dev>, <linux-kernel@vger.kernel.org>,
	<stable@vger.kernel.org>
Subject: Re: [PATCH] fs/ntfs3: validate the EA value length in ntfs_read_ea()
Date: Wed, 28 Oct 2026 08:40:11 +0100	[thread overview]
Message-ID: <5593517a-a75e-4e03-b74e-0399069c03c5@paragon-software.com> (raw)
In-Reply-To: <20260803043432.94886-1-syjcnss@gmail.com>

On 8/3/26 06:34, Yuejie Shi wrote:

> ntfs_read_ea() checks the $EA list for consistency, but it only
> recomputes the packed size of an entry when the entry's own size field is
> zero:
>
>          for (off = 0; off < size; off += ea_size) {
>                  const struct EA_FULL *ef = Add2Ptr(ea_p, off);
>                  u32 bytes = size - off;
>                  ...
>                  if (ef->size) {
>                          ea_size = le32_to_cpu(ef->size);
>                          if (ea_size > bytes)
>                                  goto out1;
>                          continue;              /* <- elength never checked */
>                  }
>                  ...
>                  ea_size = ALIGN(struct_size(ef, name,
>                                              1 + ef->name_len +
>                                                      le16_to_cpu(ef->elength)),
>                                  4);
>                  if (ea_size > bytes)
>                          goto out1;
>          }
>
> ef->size is non-zero for every entry except the last one, and it is what
> ntfs3 itself writes (ntfs_set_ea() stores exactly that aligned packed
> size), so in practice elength is not validated at all.  find_ea() then
> uses unpacked_ea_size(), which also prefers ea->size when it is set, so
> it agrees and hands the entry back.
>
> ntfs_get_ea() copies elength bytes with no further bound of its own:
>
>          len = le16_to_cpu(ea->elength);
>          ...
>          if (len > size) {              /* size is the *caller's* buffer */
>                  err = -ERANGE;
>                  ...
>          }
>          memcpy(buffer, ea->name + ea->name_len + 1, len);
>
> so a caller that supplies a large enough buffer receives up to 64 KiB of
> whatever follows the small $EA allocation -- straight to userspace via
> getxattr(2).
>
> Compute the packed size unconditionally and require it to fit in the
> entry.  Hoist the "bytes < offsetof(struct EA_FULL, name)" test above the
> ef->size branch as well: name_len and elength were being read there
> without it, which is itself a small over-read of the kmalloc'd buffer
> when fewer than 8 bytes are left.  The old "bytes < sizeof(ef->size)"
> test is subsumed by it.
>
>    # mkntfs image; create /mnt/ntfs/poc with user.ntfs6 set, then rewrite
>    # that EA_FULL's elength from 0xf to 0xff00 on the raw device
>    mount -t ntfs3 -o ro /dev/vda /mnt/ntfs
>    getxattr("/mnt/ntfs/poc", "user.ntfs6", buf, 128 * 1024);
>
>    BUG: KASAN: slab-out-of-bounds in ntfs_get_ea+0x294/0x3e0
>    Read of size 65280 at addr ffff0000cca53acf by task xattr_probe/142
>     __asan_memcpy+0x3c/0xa0
>     ntfs_get_ea+0x294/0x3e0
>     ntfs_getxattr+0x284/0x318
>     __vfs_getxattr+0x104/0x160
>     vfs_getxattr+0x1b4/0x1e0
>     do_getxattr+0xcc/0x230
>     path_getxattrat+0x184/0x270
>     __arm64_sys_getxattr+0x64/0x80
>    Allocated by task 142:
>     __kmalloc_noprof+0x294/0x668
>     ntfs_read_ea+0x1e4/0x420
>     ntfs_get_ea+0x2c8/0x3e0
>    The buggy address belongs to the object at ffff0000cca53a80
>     which belongs to the cache kmalloc-96 of size 96
>
> The object being over-read is the one ntfs_read_ea() just allocated, and
> it is 96 bytes.
>
> Entries written by ntfs3 and by Windows set size to the aligned packed
> size, so the new test does not reject anything a working filesystem
> produces.
>
> Fixes: be71b5cba2e6 ("fs/ntfs3: Add attrib operations")
> Cc: stable@vger.kernel.org
> Signed-off-by: Yuejie Shi <syjcnss@gmail.com>
> ---
>   fs/ntfs3/xattr.c | 27 ++++++++++++++++-----------
>   1 file changed, 16 insertions(+), 11 deletions(-)
>
> diff --git a/fs/ntfs3/xattr.c b/fs/ntfs3/xattr.c
> index 04814dd29375..e67a1100d0cd 100644
> --- a/fs/ntfs3/xattr.c
> +++ b/fs/ntfs3/xattr.c
> @@ -146,26 +146,31 @@ static int ntfs_read_ea(struct ntfs_inode *ni, struct
>          for (off = 0; off < size; off += ea_size) {
>                  const struct EA_FULL *ef = Add2Ptr(ea_p, off);
>                  u32 bytes = size - off;
> +               u32 packed_size;
>
> -               /* Check if we can use field ea->size. */
> -               if (bytes < sizeof(ef->size))
> +               /* Check if we can use fields ef->name_len and ef->elength. */
> +               if (bytes < offsetof(struct EA_FULL, name))
>                          goto out1;
>
> +               /*
> +                * The name and the value have to fit in the entry whether or
> +                * not ef->size is set, because ntfs_get_ea() copies elength
> +                * bytes out of this buffer and checks them only against the
> +                * caller supplied buffer size.
> +                */
> +               packed_size = ALIGN(struct_size(ef, name,
> +                                               1 + ef->name_len +
> +                                                       le16_to_cpu(ef->elength)),
> +                                   4);
> +
>                  if (ef->size) {
>                          ea_size = le32_to_cpu(ef->size);
> -                       if (ea_size > bytes)
> +                       if (ea_size > bytes || packed_size > ea_size)
>                                  goto out1;
>                          continue;
>                  }
>
> -               /* Check if we can use fields ef->name_len and ef->elength. */
> -               if (bytes < offsetof(struct EA_FULL, name))
> -                       goto out1;
> -
> -               ea_size = ALIGN(struct_size(ef, name,
> -                                           1 + ef->name_len +
> -                                                   le16_to_cpu(ef->elength)),
> -                               4);
> +               ea_size = packed_size;
>                  if (ea_size > bytes)
>                          goto out1;
>          }
> --
> 2.51.0

Hello,

The patch was applied, thank you.

Regards,
Konstantin


      reply	other threads:[~2026-10-28  7:40 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-03  4:34 Yuejie Shi
2026-10-28  7:40 ` Konstantin Komarov [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=5593517a-a75e-4e03-b74e-0399069c03c5@paragon-software.com \
    --to=almaz.alexandrovich@paragon-software.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=ntfs3@lists.linux.dev \
    --cc=stable@vger.kernel.org \
    --cc=syjcnss@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®