[PATCH AUTOSEL 6.19-5.15] fs: ntfs3: fix infinite loop triggered by zero-sized ATTR_LIST

From: Sasha Levin

Date: Fri Feb 20 2026 - 07:39:17 EST


From: Jaehun Gou <p22gone@xxxxxxxxx>

[ Upstream commit 06909b2549d631a47fcda249d34be26f7ca1711d ]

We found an infinite loop bug in the ntfs3 file system that can lead to a
Denial-of-Service (DoS) condition.

A malformed NTFS image can cause an infinite loop when an ATTR_LIST attribute
indicates a zero data size while the driver allocates memory for it.

When ntfs_load_attr_list() processes a resident ATTR_LIST with data_size set
to zero, it still allocates memory because of al_aligned(0). This creates an
inconsistent state where ni->attr_list.size is zero, but ni->attr_list.le is
non-null. This causes ni_enum_attr_ex to incorrectly assume that no attribute
list exists and enumerates only the primary MFT record. When it finds
ATTR_LIST, the code reloads it and restarts the enumeration, repeating
indefinitely. The mount operation never completes, hanging the kernel thread.

This patch adds validation to ensure that data_size is non-zero before memory
allocation. When a zero-sized ATTR_LIST is detected, the function returns
-EINVAL, preventing a DoS vulnerability.

Co-developed-by: Seunghun Han <kkamagui@xxxxxxxxx>
Signed-off-by: Seunghun Han <kkamagui@xxxxxxxxx>
Co-developed-by: Jihoon Kwon <kjh010315@xxxxxxxxx>
Signed-off-by: Jihoon Kwon <kjh010315@xxxxxxxxx>
Signed-off-by: Jaehun Gou <p22gone@xxxxxxxxx>
Signed-off-by: Konstantin Komarov <almaz.alexandrovich@xxxxxxxxxxxxxxxxxxxx>
Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx>
---

LLM Generated explanations, may be completely bogus:

Now I have a comprehensive understanding. Let me write up the analysis.

## Analysis

### 1. Commit Message Analysis

The commit explicitly describes an **infinite loop bug** (DoS
vulnerability) in the ntfs3 filesystem. A malformed NTFS image with a
zero-sized `ATTR_LIST` attribute causes the mount operation to hang
indefinitely, consuming a kernel thread. The commit message is detailed
and explains the exact mechanism clearly.

### 2. Code Change Analysis

The fix adds **two simple zero-size validation checks** — one for
resident and one for non-resident `ATTR_LIST` attributes:

- **Resident path** (line ~54): After reading `lsize =
le32_to_cpu(attr->res.data_size)`, if `lsize == 0`, return `-EINVAL`.
- **Non-resident path** (line ~71): After reading `lsize =
le64_to_cpu(attr->nres.data_size)`, if `lsize == 0`, return `-EINVAL`.

Both checks use the existing `goto out` error path and follow the
existing error handling pattern.

### 3. Root Cause (Verified)

The subagent analysis confirmed the exact mechanism:

1. `al_aligned(0)` returns 1024 (not 0), so memory gets allocated even
for zero-size data.
2. `ni->attr_list.size` is set to 0, but `ni->attr_list.le` becomes non-
NULL.
3. `ni_enum_attr_ex()` in `fs/ntfs3/frecord.c:259` checks `if
(!ni->attr_list.size)` — this is true, so it only enumerates the
primary MFT record.
4. Finding `ATTR_LIST` in the primary record triggers a reload via
`ntfs_load_attr_list()`, which returns 0 early (size already set to 0
but le is allocated), creating an infinite loop.

### 4. Stable Criteria Assessment

| Criterion | Assessment |
|-----------|-----------|
| Obviously correct | **YES** — A zero-sized attribute list is invalid;
rejecting it with -EINVAL is the right thing to do |
| Fixes a real bug | **YES** — Infinite loop / kernel hang on mount
(DoS) |
| Important issue | **YES** — DoS vulnerability from crafted filesystem
image |
| Small and contained | **YES** — Only adds 8 lines (two 4-line checks)
in a single file |
| No new features | **YES** — Pure input validation |
| Applies cleanly | **Likely** — The surrounding code has been stable;
minor context changes from kvmalloc/kvfree patches may need checking |

### 5. Risk Assessment

- **Risk: VERY LOW** — Two simple `if (!lsize)` checks that return an
error. Cannot introduce regressions because a zero-sized ATTR_LIST is
inherently invalid on a well-formed NTFS volume.
- **Benefit: HIGH** — Prevents a kernel hang (DoS) from a crafted NTFS
image. This is security-relevant as NTFS images can come from
untrusted sources (USB drives, network shares, etc.).

### 6. Affected Versions

The ntfs3 driver was introduced in v5.15 (commit `be71b5cba2e6`). The
vulnerable `ntfs_load_attr_list()` function has existed since the
driver's introduction. All stable trees with ntfs3 (5.15.y, 6.1.y,
6.6.y, 6.12.y) are affected.

### 7. Dependencies

The fix is self-contained — no dependencies on other patches. It uses
only existing error handling patterns (`goto out` with `err = -EINVAL`).

### Verification

- **git log** confirmed `attrlist.c` has been in the tree since commit
`be71b5cba2e6` ("fs/ntfs3: Add attrib operations")
- **git merge-base** verified ntfs3 is present in v5.15, confirming all
current stable trees are affected
- **Subagent analysis** of `al_aligned()` in
`fs/ntfs3/ntfs_fs.h:484-487` confirmed `al_aligned(0)` returns 1024
- **Subagent analysis** of `ni_enum_attr_ex()` in
`fs/ntfs3/frecord.c:259` confirmed the infinite loop mechanism via the
`.size == 0` check
- **Subagent analysis** of `ntfs_load_attr_list()` callers confirmed
it's called from `fs/ntfs3/inode.c:187-192` during inode loading
- **git show 06909b2549d63** confirmed the commit exists and matches the
described fix
- The claim that this is a DoS vulnerability is verified by the code
analysis showing the infinite loop mechanism

This is a textbook stable backport candidate: a small, surgical fix for
a DoS vulnerability (infinite loop / kernel hang) triggered by a
malformed filesystem image, with zero risk of regression.

**YES**

fs/ntfs3/attrlist.c | 9 +++++++++
1 file changed, 9 insertions(+)

diff --git a/fs/ntfs3/attrlist.c b/fs/ntfs3/attrlist.c
index a4d74bed74fab..098bd7e8c3d64 100644
--- a/fs/ntfs3/attrlist.c
+++ b/fs/ntfs3/attrlist.c
@@ -52,6 +52,11 @@ int ntfs_load_attr_list(struct ntfs_inode *ni, struct ATTRIB *attr)

if (!attr->non_res) {
lsize = le32_to_cpu(attr->res.data_size);
+ if (!lsize) {
+ err = -EINVAL;
+ goto out;
+ }
+
/* attr is resident: lsize < record_size (1K or 4K) */
le = kvmalloc(al_aligned(lsize), GFP_KERNEL);
if (!le) {
@@ -66,6 +71,10 @@ int ntfs_load_attr_list(struct ntfs_inode *ni, struct ATTRIB *attr)
u16 run_off = le16_to_cpu(attr->nres.run_off);

lsize = le64_to_cpu(attr->nres.data_size);
+ if (!lsize) {
+ err = -EINVAL;
+ goto out;
+ }

run_init(&ni->attr_list.run);

--
2.51.0