[PATCH AUTOSEL 6.18-5.15] fs/ntfs3: fix lseek EINVAL on sparse/compressed files with 64-bit clusters

From: Sasha Levin

Date: Mon Oct 19 2026 - 11:16:22 EST


From: Senjin <senjin@xxxxxxxxxxxxx>

[ Upstream commit 789af83e41aef54c1ff1cee16adc27822e0a0d71 ]

When CONFIG_NTFS3_64BIT_CLUSTER is enabled, sbi->maxbytes_sparse is set
to -1. As a signed loff_t this is -1LL, the most negative value. Any
lseek on a sparse or compressed file passes this as maxsize to
vfs_setpos(), which returns -EINVAL whenever offset > maxsize, and
since -1LL is less than any non-negative offset, every seek fails,
including lseek(fd, 0, SEEK_SET).

The intent of -1 here appears to be "no limit" (matching the spirit of
MAX_LFS_FILESIZE assigned to sbi->maxbytes and sb->s_maxbytes in the
same block), but the signed type makes it the minimum instead of the
maximum.

Fix by assigning MAX_LFS_FILESIZE to sbi->maxbytes_sparse in the
64-bit cluster path, consistent with the other two limits set there.

Observed on a 16 TB NTFS volume with 0xFFFFFEFF total clusters compiled
with CONFIG_NTFS3_64BIT_CLUSTER=y. Sequential reads via dd/cp worked
correctly; any lseek call on sparse files returned EINVAL, preventing
archive managers and other tools from random-accessing files on the
volume. The non-64-bit-cluster path correctly sets maxbytes_sparse to
(1ull << (cluster_bits + 32)) - 1, a large positive value.

Signed-off-by: Senjin <senjin@xxxxxxxxxxxxx>
Signed-off-by: Konstantin Komarov <almaz.alexandrovich@xxxxxxxxxxxxxxxxxxxx>
Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx>
---

LLM Generated explanations, may be completely bogus:

This is a one-line NTFS3 correctness fix: with 64-bit clusters,
sparse/compressed `lseek` was passing a signed `-1` limit into
`vfs_setpos()`, so every nonnegative seek returned `-EINVAL`. The local
tree is **7.3.0-rc4** (`v7.3-rc4-27-gf0100363d8c37`); the path and the
bug both exist here.

---

## PHASE 1: COMMIT MESSAGE FORENSICS

**Step 1.1: PARSE THE SUBJECT LINE**
Record: `[fs/ntfs3] [fix] [lseek returns EINVAL on sparse/compressed
files when CONFIG_NTFS3_64BIT_CLUSTER is set]`

**Step 1.2: PARSE ALL COMMIT MESSAGE TAGS**
Record:
- `Signed-off-by: Senjin <senjin@xxxxxxxxxxxxx>` (author)
- `Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-
software.com>` (NTFS3 maintainer; listed in MAINTAINERS)
- No `Fixes:`, `Reported-by:`, `Tested-by:`, `Reviewed-by:`, `Acked-
by:`, `Link:`, or `Cc: stable@` (absence is expected for this review)

**Step 1.3: ANALYZE THE COMMIT BODY TEXT**
Record:
- **Bug:** With `CONFIG_NTFS3_64BIT_CLUSTER`, `sbi->maxbytes_sparse` is
set to `-1`. Used as signed `loff_t`, that is `-1LL`. `ntfs_llseek()`
passes it to `vfs_setpos()` / `generic_file_llseek_size()`, which
rejects any `offset > maxsize`.
- **Symptom:** Every `lseek` on sparse/compressed files fails with
`EINVAL`, including `lseek(fd, 0, SEEK_SET)`. Sequential `dd`/`cp`
still work. Archive managers cannot random-access those files.
- **Trigger:** Observed on a 16 TB NTFS volume (`0xFFFFFEFF` clusters)
with `CONFIG_NTFS3_64BIT_CLUSTER=y`.
- **Root cause:** `-1` was meant as “no limit” (same idea as
`MAX_LFS_FILESIZE` for `sb->s_maxbytes`), but as a signed max it is
the minimum, not the maximum.
- **Fix:** Assign `MAX_LFS_FILESIZE` instead, matching `sb->s_maxbytes`
in the same `#ifdef`.

**Step 1.4: DETECT HIDDEN BUG FIXES**
Record: `[Not hidden]` — subject and body describe a real, reproduced
functional bug.

---

## PHASE 2: DIFF ANALYSIS - LINE BY LINE

**Step 2.1: INVENTORY THE CHANGES**
Record:
- File: `fs/ntfs3/super.c` — 1 insertion, 1 deletion
- Function: `ntfs_init_from_boot()`
- Scope: single-file surgical one-liner

**Step 2.2: UNDERSTAND THE CODE FLOW CHANGE**
Record:
- **Before:** `#ifdef CONFIG_NTFS3_64BIT_CLUSTER` always does
`sbi->maxbytes_sparse = -1`.
- **After:** `sbi->maxbytes_sparse = MAX_LFS_FILESIZE`.
- Path: superblock init at mount. Affects every later
`ntfs_get_maxbytes()` use for sparse/compressed inodes.

**Step 2.3: IDENTIFY THE BUG MECHANISM**
Record: `[Logic / type-signedness]`

Verified types and call path:
- `maxbytes_sparse` is `u64` (`fs/ntfs3/ntfs_fs.h`). Assigning `-1`
stores `U64_MAX`.
- `ntfs_get_maxbytes()` returns that as `loff_t`. `U64_MAX` → `-1LL`.
- `ntfs_llseek()` (`fs/ntfs3/file.c`) passes that to
`generic_file_llseek_size()` / `vfs_setpos()`.
- `vfs_setpos_cookie()` (`fs/read_write.c`) does `if (offset > maxsize)
return -EINVAL`. For `offset == 0` and `maxsize == -1`, `0 > -1` is
true.

Same conversion also breaks `ntfs_iomap_begin()`’s `IOMAP_REPORT` check
(`offset > ntfs_get_maxbytes(ni)`), so fiemap/bmap on those files can
fail the same way.

The `#else` path already uses a large positive `(1ull << (cluster_bits +
32)) - 1`.

**Step 2.4: ASSESS THE FIX QUALITY**
Record:
- Obviously correct: `MAX_LFS_FILESIZE` is `LLONG_MAX` on 64-bit
(`include/linux/fs.h`); this config is 64-bit-only (`#error` on
32-bit). Same constant is already assigned to `sb->s_maxbytes` in this
block.
- Minimal: one assignment change, no API/struct change.
- Regression risk: very low. Limit goes from “every seek fails” to the
VFS max file size. `ntfs_set_size()` / attrib insert compare against
`u64`; `LLONG_MAX` is still a huge positive cap and matches VFS, vs
the old `U64_MAX` which was accidentally unlimited on those u64
checks.

---

## PHASE 3: GIT HISTORY INVESTIGATION

**Step 3.1: BLAME THE CHANGED LINES**
Record:
- Buggy assignment introduced in `82cae269cfa95` (“fs/ntfs3: Add
initialization of super block”, 2021-08-13), first in **v5.15-rc1**.
- Field has been `u64 maxbytes_sparse` since the original headers
(`4534a70b7056f`).
- User-visible `lseek` breakage requires `ntfs_llseek()` using
`ntfs_get_maxbytes()`, added in `c613269677283` (“implement llseek
SEEK_DATA/SEEK_HOLE…”), contained in **v7.0-rc1**. Before that,
`.llseek = generic_file_llseek` used `sb->s_maxbytes`
(`MAX_LFS_FILESIZE` in this config), so SEEK_SET worked.

**Step 3.2: FOLLOW THE FIXES: TAG**
Record: `[N/A — no Fixes: tag]`. Effective buggy commit for the reported
symptom is `c613269677283` (in this tree). The `-1` store itself is from
`82cae269cfa95` (also in this tree).

**Step 3.3: CHECK FILE HISTORY FOR RELATED CHANGES**
Record:
- Candidate: `789af83e41aef` (2026-06-13 / committed 2026-07-24).
- Merged to Linus in `dc83d18cdd904` (`ntfs3_for_7.3`, 2026-08-20);
present in **v7.3-rc1**. **Not** in v7.2 / v7.1 / v7.0.
- Series sibling (v2 1/2): `e31886cb6e0e1` (ATTR_BITMAP extents) is the
git parent but touches `fs/ntfs3/inode.c` only. Independent of this
hunk.
- No other `maxbytes_sparse` fix in this tree.

**Step 3.4: CHECK THE AUTHOR'S OTHER COMMITS**
Record: Senjin has this plus the ATTR_BITMAP mount fix on the same 16 TB
volume. Konstantin Komarov is the NTFS3 maintainer (`MAINTAINERS`:
Supported) and committed both.

**Step 3.5: CHECK FOR DEPENDENT/PREREQUISITE COMMITS**
Record: Standalone. Needs only `CONFIG_NTFS3_64BIT_CLUSTER` and
`MAX_LFS_FILESIZE`, both present since ntfs3 was added. Does not need
patch 1/2.

---

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

**Step 4.1: FIND THE ORIGINAL PATCH DISCUSSION**
Record:
- `b4 dig -c 789af83e41aef`: `[PATCH v2 2/2]`,
https://patch.msgid.link/6a2dba8e030b48618aa54661@xxxxxxxxxxxxxx
- `b4 dig -a`: v1 was only the ATTR_BITMAP patch (2026-06-10); v2 added
this as 2/2 (2026-06-13). Applied version is v2.
- lore.kernel.org / patch.msgid.link returned **403**; spinics ntfs3
thread returned **500**. Patch text confirmed via LKML IU and Openwall
mirrors. Full reply thread: **UNVERIFIED**.

**Step 4.2: CHECK WHO REVIEWED THE PATCH**
Record (`b4 dig -w`): `senjin@xxxxxxxxxxxxx`, `ntfs3@xxxxxxxxxxxxxxx`,
`almaz.alexandrovich@xxxxxxxxxxxxxxxxxxxx`, `linux-
kernel@xxxxxxxxxxxxxxx`. Maintainer list was CCed; maintainer signed off
and merged. No NAK found in reachable archives.

**Step 4.3: SEARCH FOR THE BUG REPORT**
Record: No syzbot/bugzilla link. Author reports a real 16 TB volume;
sequential I/O worked, `lseek` did not. Same volume as the ATTR_BITMAP
mount failure.

**Step 4.4: CHECK FOR RELATED PATCHES AND SERIES**
Record: v2 1/2 is a different mount bug (`inode.c`). This 2/2 is self-
contained. Linus’s `ntfs3_for_7.3` pull lists “lseek EINVAL on
sparse/compressed files with 64-bit clusters” under Fixed.

**Step 4.5: CHECK STABLE MAILING LIST HISTORY**
Record: lore.kernel.org/stable search did not turn up this subject
(403/unrelated hits). **UNVERIFIED** whether anyone already
requested/rejected it for stable.

---

## PHASE 5: CODE SEMANTIC ANALYSIS

**Step 5.1: IDENTIFY KEY FUNCTIONS IN THE DIFF**
Record: `ntfs_init_from_boot()` (assignment). Consumers:
`ntfs_get_maxbytes()` → `ntfs_llseek()`, `ntfs_iomap_begin()`; also raw
`sbi->maxbytes_sparse` in `ntfs_set_size()` and attrib insert.

**Step 5.2: TRACE CALLERS**
Record:
- `ntfs_init_from_boot()` — mount/fill_super.
- `ntfs_llseek` — `file_operations.llseek` for regular ntfs3 files
(syscall `lseek`/`llseek`).
- `ntfs_iomap_begin` — fiemap/bmap `IOMAP_REPORT`.
- Directory `llseek` still uses `generic_file_llseek` (`dir.c`); not
this bug.

**Step 5.3: TRACE CALLEES**
Record: The changed line is an assignment. Downstream: `vfs_setpos()` /
`generic_file_llseek_size()`.

**Step 5.4: FOLLOW THE CALL CHAIN**
Record: Userspace `lseek` → `ntfs_llseek` → `ntfs_get_maxbytes` →
`vfs_setpos`. Reachable from unprivileged processes with a file open.
Confirmed in-tree.

**Step 5.5: SEARCH FOR SIMILAR PATTERNS**
Record: `sbi->maxbytes = -1` remains in the overflow branch (`clusters
>= 1ull << (64 - cluster_bits)`). Same signedness issue for *non-sparse*
files, but that volume size is far beyond the reported 16 TB /
`0xFFFFFEFF` clusters. Out of this patch’s scope.

---

## PHASE 6: CROSS-REFERENCING AGAINST THE LOCAL TREE

**Step 6.1: DOES THE BUGGY CODE EXIST IN THIS TREE?**
Record: Yes. `fs/ntfs3/super.c`, `ntfs_llseek`,
`CONFIG_NTFS3_64BIT_CLUSTER`, and `ntfs_get_maxbytes()` are all present.
Current line is already the fixed assignment (`sbi->maxbytes_sparse =
MAX_LFS_FILESIZE` at line 1214). Pre-fix `-1` is in `789af83e41aef^`.
Bug introduction for lseek: v7.0; this checkout is 7.3-rc4.

**Step 6.2: CHECK FOR BACKPORT COMPLICATIONS**
Record: Clean apply. Surrounding `#ifdef CONFIG_NTFS3_64BIT_CLUSTER` /
`sb->s_maxbytes = MAX_LFS_FILESIZE` matches the hunk. No conflicting
refactor in this function.

**Step 6.3: CHECK IF RELATED FIXES ARE ALREADY HERE**
Record: This commit *is* the fix (`789af83e41aef` is an ancestor of
HEAD). No earlier substitute fix. That is origin-tree presence, not a
reason to reject on merit.

---

## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT

**Step 7.1: IDENTIFY THE SUBSYSTEM AND ITS CRITICALITY**
Record: `[fs/ntfs3] [IMPORTANT]` — not core VFS, but a supported RW
filesystem used for Windows/large volumes. Maintainer-supported.

**Step 7.2: ASSESS SUBSYSTEM ACTIVITY**
Record: Actively maintained (regular `ntfs3_for_*` pulls, many 7.3
fixes). This is not staging.

---

## PHASE 8: IMPACT AND RISK ASSESSMENT

**Step 8.1: DETERMINE WHO IS AFFECTED**
Record: Config-specific (`CONFIG_NTFS3_64BIT_CLUSTER=y`, 64-bit,
recommended N in Kconfig) **and** sparse or compressed files. That
option exists specifically for 16T+ volumes with 4K clusters (Windows
ntfs.sys is 32-bit cluster). Real users of large NTFS volumes.

**Step 8.2: DETERMINE THE TRIGGER CONDITIONS**
Record: Any `lseek` (SEEK_SET/SEEK_END/nonzero SEEK_CUR) on a
sparse/compressed file after mount. Common for archive tools, not rare
error-path. Unprivileged if the user can open the file. Not a race.

**Step 8.3: DETERMINE THE FAILURE MODE SEVERITY**
Record: `[Failure: all random access via lseek fails with EINVAL;
sequential read still works] [Severity: HIGH]`. Not a panic/UAF, but the
filesystem is unusable for a normal class of tools. Also breaks
fiemap/bmap `IOMAP_REPORT` on those inodes.

**Step 8.4: CALCULATE RISK-BENEFIT RATIO**
Record: Benefit high for affected mounts (restores `lseek`). Risk very
low (1 line, same constant already used for `s_maxbytes`). Ratio
strongly favors backport.

---

## PHASE 9: FINAL SYNTHESIS

**Step 9.1: COMPILE THE EVIDENCE**

**FOR:**
- Real user-visible bug on a 16 TB volume; `lseek` always `EINVAL` on
sparse/compressed files.
- Mechanism verified in this tree (`vfs_setpos` `offset > maxsize`,
signed `-1`).
- One-line, obviously correct, matches existing `MAX_LFS_FILESIZE`
usage.
- Maintainer signed off and merged.
- Standalone; no new API/feature.
- Call path is a normal syscall.

**AGAINST:**
- Option defaults to N; smaller population.
- Not a crash/UAF/corruption.
- Series labeled 2/2 (sibling is unrelated and not required).
- Full lore review thread not readable (403/500).

**UNRESOLVED:** Full reviewer comments; any stable@ discussion.

**Step 9.2: APPLY THE STABLE RULES CHECKLIST**
1. Obviously correct and tested? **PASS** — type/`vfs_setpos` logic is
clear; author observed it on real hardware; maintainer merged.
2. Fixes a real bug that affects users? **PASS** — reproduced; tools
cannot seek.
3. Important issue? **PASS** — HIGH: filesystem random-access completely
broken for sparse/compressed files on this config (not a panic, but a
serious functional failure).
4. Small and contained? **PASS** — 1 line, one file, one assignment.
5. No new features or APIs? **PASS**.
6. Can apply to the local tree? **PASS** — hunk matches
`ntfs_init_from_boot()` here.

**Step 9.3: CHECK FOR EXCEPTION CATEGORIES**
Record: `[None]` — ordinary bugfix, not a device-ID/quirk/DT/build/doc
exception.

**Step 9.4: MAKE YOUR DECISION**
This tree has the ntfs3 64-bit-cluster init path and
`ntfs_llseek()`/`ntfs_get_maxbytes()`. The signed `-1` max makes every
sparse/compressed seek fail. Replacing it with `MAX_LFS_FILESIZE` is the
right, minimal fix and meets stable rules.

---

### What the commit solves and why it matters here
On `CONFIG_NTFS3_64BIT_CLUSTER=y`, sparse/compressed files cannot be
seeked at all. That breaks archive managers and any random-access reader
while sequential copy still works. This checkout is 7.3, which already
has the 7.0 `ntfs_llseek` path, so the bug is in this codebase.

### Stable rules
Small, correct, user-reported functional failure, no new API, applies
cleanly.

### Risk vs benefit
Very low risk, high benefit for large NTFS volumes built with 64-bit
clusters.

### Concerns
Kconfig recommends N, so not every ntfs3 user. Patch 1/2 is not a
dependency. Residual `sbi->maxbytes = -1` overflow branch is a separate
edge case.

---

Verification:
- [Phase 1] Parsed commit `789af83e41aef`: SOB Senjin + maintainer
Komarov; no Fixes/Cc stable/Reported-by
- [Phase 2] Diff: `sbi->maxbytes_sparse = -1` → `MAX_LFS_FILESIZE` in
`ntfs_init_from_boot()`; 1 line
- [Phase 2] Confirmed `maxbytes_sparse` is `u64`; `ntfs_get_maxbytes()`
returns `loff_t`; `vfs_setpos_cookie()` rejects `offset > maxsize`
- [Phase 2] Confirmed `ntfs_llseek()` and `ntfs_iomap_begin()` consume
that value
- [Phase 3] `git blame`: `-1` from `82cae269cfa95` (v5.15-rc1);
`ntfs_llseek` from `c613269677283` (v7.0-rc1)
- [Phase 3] `git merge-base --is-ancestor 789af83e41aef HEAD`: yes; in
v7.3-rc1; not in v7.2/v7.1/v7.0
- [Phase 3] Series 1/2 `e31886cb6e0e1` touches `inode.c` only; not
required
- [Phase 4] `b4 dig -c/-a/-w`: v2 2/2, maintainer+ntfs3 list CCed; lore
fetch 403
- [Phase 4] LKML IU / Openwall: patch text matches; no NAK in those
copies
- [Phase 4] UNVERIFIED: full lore thread, stable@ discussion
- [Phase 5] Callers: `ntfs_llseek` (syscall), `ntfs_iomap_begin`
(fiemap)
- [Phase 6] `git describe HEAD`: `v7.3-rc4-27-gf0100363d8c37`; `make
kernelversion`: 7.3.0-rc4; current `super.c:1214` already has the fix
- [Phase 6] `CONFIG_NTFS3_64BIT_CLUSTER` in `fs/ntfs3/Kconfig`, 64-bit
only
- [Phase 7] MAINTAINERS: Konstantin Komarov, Supported
- [Phase 8] Failure: every sparse/compressed `lseek` → `-EINVAL`;
severity HIGH; trigger is normal userspace seek

**YES**

fs/ntfs3/super.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/ntfs3/super.c b/fs/ntfs3/super.c
index 346c52f952076..dcf14410c6621 100644
--- a/fs/ntfs3/super.c
+++ b/fs/ntfs3/super.c
@@ -1163,7 +1163,7 @@ static int ntfs_init_from_boot(struct super_block *sb, u32 sector_size,
#ifdef CONFIG_NTFS3_64BIT_CLUSTER
if (clusters >= (1ull << (64 - cluster_bits)))
sbi->maxbytes = -1;
- sbi->maxbytes_sparse = -1;
+ sbi->maxbytes_sparse = MAX_LFS_FILESIZE;
sb->s_maxbytes = MAX_LFS_FILESIZE;
#else
/* Maximum size for sparse file. */
--
2.53.0