[PATCH AUTOSEL 7.0-5.10] exfat: Fix bitwise operation having different size

Sasha Levin
2026-04-20T13:24:58Z

From: Philipp Hahn <phahn-oss@avm.de>

[ Upstream commit 3dce5bb82c97fc2ac28d80d496120a6525ce3fb7 ]

cpos has type loff_t (long long), while s_blocksize has type u32. The
inversion wil happen on u32, the coercion to s64 happens afterwards and
will do 0-left-paddding, resulting in the upper bits getting masked out.

Cast s_blocksize to loff_t before negating it.

Found by static code analysis using Klocwork.

Signed-off-by: Philipp Hahn <phahn-oss@avm.de>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

Good. Now I have all the information needed for the complete analysis.

---

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: PARSE THE SUBJECT LINE
Record: [subsystem: exfat (fs/exfat)] [action verb: Fix] [summary: Fix a
type mismatch in bitwise operation where `~(sb->s_blocksize - 1)`
operates at `unsigned long` width, clearing upper bits of 64-bit `cpos`]

### Step 1.2: PARSE ALL COMMIT MESSAGE TAGS
- **Signed-off-by: Philipp Hahn <phahn-oss@avm.de>** - author from AVM
(router/embedded device vendor, makes Fritz!Box etc.)
- **Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>** - exfat
subsystem maintainer accepted the patch
- No Fixes: tag (expected for candidates under review)
- No Cc: stable tag (expected)
- No Reported-by: (found by static analysis)
- No Link: tag

Record: Minimal tags. Author is from an embedded device company.
Maintainer signed off.

### Step 1.3: ANALYZE THE COMMIT BODY TEXT
The commit describes:
- `cpos` is `loff_t` (long long, 64-bit)
- `s_blocksize` is `unsigned long` (32-bit on 32-bit platforms)
- The `~` (bitwise NOT) operates at `unsigned long` width
- When the result is coerced to `loff_t`, zero-extension clears upper 32
bits
- Fix: cast `s_blocksize` to `loff_t` before negation

Record: Bug mechanism is clearly explained. Found by Klocwork static
analysis. This is a C integer promotion/type width bug on 32-bit
platforms.

### Step 1.4: DETECT HIDDEN BUG FIXES
Record: This is an explicitly stated bug fix, not hidden. The word "Fix"
is in the subject.

---

## PHASE 2: DIFF ANALYSIS - LINE BY LINE

### Step 2.1: INVENTORY THE CHANGES
- **File:** `fs/exfat/dir.c`
- **Change:** 1 line modified (replace `~(sb->s_blocksize - 1)` with
`~(loff_t)(sb->s_blocksize - 1)`)
- **Function modified:** `exfat_iterate()` (line 252)
- **Scope:** Single-file, single-line surgical fix

Record: Minimal change. One file, one line, one cast added.

### Step 2.2: UNDERSTAND THE CODE FLOW CHANGE
The changed line is in `exfat_iterate()` at the error recovery path when
`exfat_readdir()` returns `-EIO`:

```c
if (err == -EIO) {
cpos += 1 << (sb->s_blocksize_bits);
cpos &= ~(loff_t)(sb->s_blocksize - 1); // <-- fix here
}
```

**Before:** `~(sb->s_blocksize - 1)` operates at `unsigned long` width.
On 32-bit: produces 32-bit mask, zero-extended to 64 bits, clearing
upper 32 bits of `cpos`.
**After:** `~(loff_t)(sb->s_blocksize - 1)` operates at 64-bit width.
Upper 32 bits of `cpos` are preserved.

Record: Error recovery path. Before: incorrect masking on 32-bit. After:
correct 64-bit masking.

### Step 2.3: IDENTIFY THE BUG MECHANISM
Category: **Type / endianness bug** (specifically, integer
promotion/width bug)

On 32-bit systems, `sb->s_blocksize` is `unsigned long` = 32 bits:
- `sb->s_blocksize - 1` = 0x00000FFF (for 4K blocks)
- `~(sb->s_blocksize - 1)` = 0xFFFFF000 (32-bit unsigned)
- When AND'd with 64-bit `cpos`, this zero-extends to 0x00000000FFFFF000
- Bits 32-63 of `cpos` are incorrectly cleared

Record: Type width mismatch bug on 32-bit platforms. Incorrect zero-
extension of unsigned 32-bit mask when used with 64-bit value.

### Step 2.4: ASSESS THE FIX QUALITY
- **Obviously correct?** YES - the cast ensures the negation operates at
64-bit width
- **Minimal?** YES - one cast addition
- **Regression risk?** ZERO - identical behavior on 64-bit systems
(where `unsigned long` is already 64-bit), and correct behavior on
32-bit
- **Red flags?** None

Record: Perfect fix quality. Zero regression risk.

---

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: BLAME THE CHANGED LINES
The buggy code was introduced in commit `ca06197382bde0` by Namjae Jeon
on 2020-03-02, titled "exfat: add directory operations." This was part
of the initial exfat merge into the kernel for Linux 5.7.

Record: Bug present since initial exfat creation (v5.7, 2020). Affects
all stable trees that contain exfat (5.10+, 5.15+, 6.1+, 6.6+, 6.12+,
7.0).

### Step 3.2: FOLLOW THE FIXES TAG
No Fixes: tag present. The implicit fix target is `ca06197382bde0`.

Record: N/A (no explicit Fixes: tag, which is expected).

### Step 3.3: CHECK FILE HISTORY FOR RELATED CHANGES
55 commits to `fs/exfat/dir.c` since the initial creation. The file has
been actively developed. Notable: commit `6b151eb5df78d` was a recent
cleanup of `exfat_readdir()` but did not touch the buggy line.

Record: Active file history. The buggy line has been untouched since
initial creation. No prerequisites needed.

### Step 3.4: CHECK THE AUTHOR'S OTHER COMMITS
Philipp Hahn (phahn-oss@avm.de) has 5 commits in the tree, mostly
documentation and quirk-related. AVM is a German embedded device company
(Fritz!Box routers). Not the exfat maintainer, but the maintainer
(Namjae Jeon) signed off on this fix.

Record: External contributor from embedded device company. Maintainer
accepted.

### Step 3.5: CHECK FOR DEPENDENT/PREREQUISITE COMMITS
The fix is a single cast to an existing line. No dependencies on other
commits.

Record: Fully standalone. No dependencies.

---

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

### Step 4.1-4.5: MAILING LIST INVESTIGATION
b4 dig could not find the commit (it may be very recent).
lore.kernel.org was behind Anubis anti-scraping protection. Web searches
didn't return the specific lore thread.

Record: Could not access lore discussion. The commit was signed off by
the exfat maintainer Namjae Jeon, confirming acceptance.

---

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1: IDENTIFY KEY FUNCTIONS
Modified function: `exfat_iterate()` - the VFS directory iteration
callback for exfat.

### Step 5.2: TRACE CALLERS
`exfat_iterate` is wrapped by `WRAP_DIR_ITER(exfat_iterate)` and used as
`.iterate_shared` in `exfat_dir_operations`. It's called by the VFS when
userspace reads a directory (e.g., `ls`, `readdir()`). This is a very
common operation.

### Step 5.3-5.4: CALL CHAIN
Userspace `getdents64()` syscall -> VFS `iterate_dir()` ->
`exfat_iterate()`. The buggy path is triggered when `exfat_readdir()`
returns `-EIO`.

Record: Reachable from common syscalls. Error path triggered by I/O
errors on storage media.

### Step 5.5: SEARCH FOR SIMILAR PATTERNS
The same pattern `& ~(sb->s_blocksize - 1)` with `loff_t` or `ctx->pos`
variables exists in:
- `fs/ext4/dir.c` (line 255) - same type mismatch with `ctx->pos`
- `fs/ocfs2/dir.c` (line 1912) - same pattern
- `fs/jfs/xattr.c` (multiple places)
- `fs/ntfs3/ntfs_fs.h` (line 1109) - **already fixed** with
`~(u64)(sb->s_blocksize - 1)` cast

The ntfs3 code already has this fix, confirming this is a known bug
pattern.

Record: Similar bug exists in ext4, ocfs2, jfs. ntfs3 already fixed it.

---

## PHASE 6: CROSS-REFERENCING AND STABLE TREE ANALYSIS

### Step 6.1: DOES THE BUGGY CODE EXIST IN STABLE TREES?
The buggy code was introduced in `ca06197382bde0` (v5.7). exfat exists
in all active stable trees (5.10, 5.15, 6.1, 6.6, 6.12, 7.0). The
specific buggy line at line 252 has been untouched since creation.

Record: Bug present in ALL active stable trees.

### Step 6.2: CHECK FOR BACKPORT COMPLICATIONS
The surrounding code context is clean and unchanged since the initial
creation. The patch should apply cleanly to all stable trees.

Record: Clean apply expected for all stable trees.

### Step 6.3: RELATED FIXES IN STABLE
A similar exfat overflow fix (`2e9ceb6728f1d` "exfat: fix overflow for
large capacity partition") was explicitly tagged with `Cc:
stable@vger.kernel.org # v5.19+`, establishing precedent for
type/overflow fixes in exfat going to stable.

Record: Precedent exists for similar exfat type fixes going to stable.

---

## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT

### Step 7.1: SUBSYSTEM CRITICALITY
**Subsystem:** fs/exfat - exFAT filesystem
**Criticality:** IMPORTANT - exFAT is used on SD cards, USB drives, and
external storage across millions of devices, especially embedded/IoT
devices that run 32-bit ARM.

### Step 7.2: SUBSYSTEM ACTIVITY
Very active - 55+ commits to this file, 20+ recent exfat commits.
Actively maintained by Namjae Jeon.

Record: Important subsystem, actively maintained.

---

## PHASE 8: IMPACT AND RISK ASSESSMENT

### Step 8.1: DETERMINE WHO IS AFFECTED
Users of exFAT filesystems on **32-bit systems** (ARM, MIPS). This
includes many embedded devices, IoT systems, and older hardware. 64-bit
systems are unaffected.

### Step 8.2: DETERMINE THE TRIGGER CONDITIONS
- **Platform:** 32-bit systems only
- **Trigger:** Reading a directory on an exFAT filesystem where
`exfat_readdir()` returns `-EIO` AND `cpos` > 2^32 (>4GB directory
position)
- **Likelihood:** LOW - requires very large directory + I/O error +
32-bit system
- **Unprivileged trigger:** Yes, any user can `ls` a directory

### Step 8.3: FAILURE MODE SEVERITY
When triggered, the upper 32 bits of `cpos` are zeroed, causing the
directory position to jump backward, potentially causing:
- Incorrect directory listing
- Potential infinite loop in directory iteration
- Severity: MEDIUM (incorrect behavior, potential loop)

### Step 8.4: RISK-BENEFIT RATIO
- **Benefit:** LOW-MEDIUM (fixes correctness bug on 32-bit, rare
trigger)
- **Risk:** EXTREMELY LOW (one cast addition, provably correct, zero
regression risk on 64-bit)
- **Ratio:** Strongly favorable - near-zero risk for a provable
correctness fix

Record: Benefit is low-medium, risk is near-zero. Ratio strongly favors
backporting.

---

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: COMPILE THE EVIDENCE

**FOR backporting:**
- Provably correct fix for a real C type promotion bug
- One-line change with zero regression risk
- Bug present in all stable trees since v5.7
- Similar fixes in exfat have been backported before (Cc: stable)
- ntfs3 already has the same fix applied, confirming the pattern is
recognized
- exFAT is widely used on 32-bit embedded systems (SD cards, USB drives)
- Signed off by the exfat maintainer
- Clean apply expected for all stable trees
- Author is from embedded device company (AVM) - directly affected use
case

**AGAINST backporting:**
- Very low probability trigger (32-bit + huge directory + I/O error)
- Found by static analysis, no user reports
- No Fixes: tag, no Cc: stable (expected for candidates)

### Step 9.2: STABLE RULES CHECKLIST
1. **Obviously correct and tested?** YES - trivially correct cast,
signed off by maintainer
2. **Fixes a real bug?** YES - provable type width bug on 32-bit
platforms
3. **Important issue?** BORDERLINE - low probability but real
correctness issue
4. **Small and contained?** YES - one line, one file
5. **No new features or APIs?** YES
6. **Can apply to stable trees?** YES - untouched line since initial
creation

### Step 9.3: CHECK FOR EXCEPTION CATEGORIES
No exception category applies. This is a standard correctness fix.

### Step 9.4: DECISION
The fix has essentially zero regression risk (a single type cast that is
provably correct) and fixes a real, if unlikely to trigger, bug. The
risk-benefit ratio overwhelmingly favors backporting. Similar
type/overflow fixes in exfat have been backported before. The code
exists unchanged in all stable trees.

---

## Verification

- [Phase 1] Parsed tags: Signed-off-by from author (AVM) and maintainer
(Namjae Jeon). No Fixes/Cc:stable (expected).
- [Phase 2] Diff analysis: Single line change in `exfat_iterate()`, adds
`(loff_t)` cast to ensure 64-bit mask width.
- [Phase 3] git blame: Buggy code introduced in ca06197382bde0 (v5.7,
2020-03-02), "exfat: add directory operations"
- [Phase 3] git log: 55 commits to file since creation; buggy line
untouched since initial creation
- [Phase 3] Author check: Philipp Hahn has 5 commits, external
contributor from AVM (embedded device company)
- [Phase 4] b4 dig: Could not find the commit (may be too recent). Lore
blocked by anti-scraping.
- [Phase 5] Callers: `exfat_iterate()` is the VFS `.iterate_shared`
callback, reached via `getdents64()` syscall
- [Phase 5] Similar patterns: Same bug exists in ext4/dir.c,
ocfs2/dir.c, jfs/xattr.c. ntfs3 already fixed with `~(u64)` cast.
- [Phase 6] Code exists in all active stable trees (5.10, 5.15, 6.1,
6.6, 6.12, 7.0) - verified via git history
- [Phase 6] Precedent: commit 2e9ceb6728f1d ("exfat: fix overflow for
large capacity partition") was tagged Cc: stable
- [Phase 6] Clean apply expected - buggy line unchanged since v5.7
- [Phase 7] `s_blocksize` type verified as `unsigned long` in
`include/linux/fs/super_types.h:136`
- [Phase 8] Failure mode: incorrect directory position on 32-bit,
potential loop - severity MEDIUM
- UNVERIFIED: Could not verify lore discussion or reviewer comments due
to anti-scraping protection

**YES**

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

diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c
index 3a4853693d8bf..e710dd196e2f0 100644
--- a/fs/exfat/dir.c
+++ b/fs/exfat/dir.c
@@ -249,7 +249,7 @@ static int exfat_iterate(struct file *file, struct dir_context *ctx)
*/
if (err == -EIO) {
cpos += 1 << (sb->s_blocksize_bits);
- cpos &= ~(sb->s_blocksize - 1);
+ cpos &= ~(loff_t)(sb->s_blocksize - 1);
}

err = -EIO;
--
2.53.0