From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1FEF03CF678; Mon, 20 Apr 2026 13:29:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776691772; cv=none; b=FLFYkYlZRvc7huyylXy9Rh5QCEVUBZ+qoaq/LwqFxdBvin0PdTyB38TonYTJH0BApQ+9oTX5c0yKnFhakTLwlqb/FM5Eu7lsASw4+nG7YxOFBYYTdtyxthWu7sAw1TRhRpZcspnScjfkYVP3oqoGFAoR1HrLnYFYNR3qxOyLCow= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776691772; c=relaxed/simple; bh=2tbCt5PKW497EPg5cqoGISqq56tyKFCjtX16J8VVbYk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=YR1RoQBg11TYGE5KFq9Powqwrux1PhiNzQAIyS4Lxpwz9An/S6AeKByRspelc6FJAq8Nf4xWIrzgVpomZmNP5qlv8TKJyuutxCd7j2XsizaxiWlsy5ljjJHWTXcPsD5PCh93Fr8vJbA554lO82j0mTt2T6FYg3pTFm+0CHtNFTw= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=upFpAI+A; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="upFpAI+A" Received: by smtp.kernel.org (Postfix) with ESMTPSA id D8B1BC19425; Mon, 20 Apr 2026 13:29:30 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1776691771; bh=2tbCt5PKW497EPg5cqoGISqq56tyKFCjtX16J8VVbYk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=upFpAI+ApYkbuEaTXVS6OHdPUOc9BLsDFlB1laeyomBqEY36t4UOOxW8ttw8TZBy0 fGDiTuvSKJNzK2WUfRifQ6tFo7rm+YoujeiwIgbKaftrSEmQRzCkJzgVSEfR5AtMMC QHZ5ggDPEZUALOB0QI9uwwmMWX6PViI8QD3+IUF6gQzxoh5cWo+Lult+E2OlcdZLOA tQlraxL5v50aCeKGhorqlU36qBcORHX1cMPU8kg9cKKPk9zRwPzz4/MIR110Gmiv58 RKBrbKi27L1EKl2C9dZ0h+zEvX0bBIWIX7qmrFNuH5vCsal2PyEhIrK5WuOrGoVWvR eK1HkIT2uTAFg== From: Sasha Levin To: patches@lists.linux.dev, stable@vger.kernel.org Cc: Joanne Koong , Miklos Szeredi , Sasha Levin , miklos@szeredi.hu, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH AUTOSEL 7.0-6.1] fuse: validate outarg offset and size in notify store/retrieve Date: Mon, 20 Apr 2026 09:19:38 -0400 Message-ID: <20260420132314.1023554-184-sashal@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260420132314.1023554-1-sashal@kernel.org> References: <20260420132314.1023554-1-sashal@kernel.org> Precedence: bulk X-Mailing-List: stable@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-stable: review X-Patchwork-Hint: Ignore X-stable-base: Linux 7.0 Content-Transfer-Encoding: 8bit From: Joanne Koong [ Upstream commit 65161470f95bb579a72673bf303ecf0800b9054b ] Add validation checking for outarg offset and outarg size values passed in by the server. MAX_LFS_FILESIZE is the maximum file size supported. The fuse_notify_store_out and fuse_notify_retrieve_out structs take in a uint64_t offset. Add logic to ensure: * outarg.offset is less than MAX_LFS_FILESIZE * outarg.offset + outarg.size cannot exceed MAX_LFS_FILESIZE * potential uint64_t overflow is fixed when adding outarg.offset and outarg.size. Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi Signed-off-by: Sasha Levin --- LLM Generated explanations, may be completely bogus: Now I have all the information needed for a comprehensive analysis. Let me compile my findings. --- ## PHASE 1: COMMIT MESSAGE FORENSICS ### Step 1.1: PARSE THE SUBJECT LINE - **Subsystem**: fuse (filesystem in userspace) - **Action verb**: "validate" - indicates adding missing input validation (a bug fix pattern) - **Summary**: Adds bounds checking for `outarg.offset` and `outarg.size` in FUSE notify store/retrieve paths Record: [fuse] [validate] [Add missing bounds/overflow checks on server- supplied offset and size] ### Step 1.2: PARSE ALL COMMIT MESSAGE TAGS - **Signed-off-by: Joanne Koong** - Author, active FUSE contributor (12+ fuse commits in tree) - **Signed-off-by: Miklos Szeredi** - FUSE subsystem maintainer (commits the patch) - No Fixes: tag (expected for manual review candidates) - No Reported-by, no Link, no Cc: stable Record: No bug reporter or explicit stable nomination, but authored by a known contributor and committed by the subsystem maintainer. ### Step 1.3: ANALYZE THE COMMIT BODY TEXT The commit explicitly describes three bugs: 1. `outarg.offset` can exceed `MAX_LFS_FILESIZE` (the maximum file size) 2. `outarg.offset + outarg.size` can overflow `uint64_t` (integer overflow) 3. Both structs use `uint64_t offset` and values come from the FUSE server (userspace) The failure mode is integer overflow on server-controlled data leading to incorrect computation, potentially corrupting inode metadata or causing out-of-bounds page cache access. Record: [Bug: Integer overflow and missing bounds checks on userspace- supplied values] [Failure mode: incorrect computation leading to potential data corruption or OOB access] [All kernel versions since v2.6.36 affected] [Root cause: untrusted uint64_t values not validated before arithmetic] ### Step 1.4: DETECT HIDDEN BUG FIXES This is explicitly a validation/input sanitization fix. The word "validate" directly indicates a missing safety check. This is clearly a bug fix. Record: [Clearly a bug fix - adds missing input validation on untrusted data from userspace FUSE server] --- ## PHASE 2: DIFF ANALYSIS - LINE BY LINE ### Step 2.1: INVENTORY THE CHANGES - **File**: `fs/fuse/dev.c` - 1 file changed - **Functions modified**: `fuse_notify_store()`, `fuse_retrieve()`, `fuse_notify_retrieve()` - **Scope**: ~15 lines changed (very small, surgical fix) Record: [1 file, 3 functions, ~15 lines changed - single-file surgical fix] ### Step 2.2: UNDERSTAND THE CODE FLOW CHANGE **Hunk 1 - `fuse_notify_store()`**: - BEFORE: `end = outarg.offset + outarg.size` with no overflow protection; `num = outarg.size` with no cap - AFTER: Adds `outarg.offset >= MAX_LFS_FILESIZE` check, caps `num = min(outarg.size, MAX_LFS_FILESIZE - outarg.offset)`, uses `num` instead of `outarg.size` for `end` and `fuse_write_update_attr()` **Hunk 2 - `fuse_retrieve()`**: - BEFORE: `else if (outarg->offset + num > file_size)` - addition can overflow - AFTER: `else if (num > file_size - outarg->offset)` - safe since `outarg->offset <= file_size` at this point **Hunk 3 - `fuse_notify_retrieve()`**: - BEFORE: No offset validation before passing to `fuse_retrieve()` - AFTER: Adds `outarg.offset >= MAX_LFS_FILESIZE` check, returns -EINVAL ### Step 2.3: IDENTIFY THE BUG MECHANISM Category: **Memory safety / Logic correctness** - specifically: 1. **Integer overflow**: `outarg.offset + outarg.size` wraps around uint64_t when offset is near UINT64_MAX, causing `end` to be a small value. This leads to incorrect file size update via `fuse_write_update_attr()`. 2. **Missing bounds check**: Without MAX_LFS_FILESIZE validation, `outarg.offset >> PAGE_SHIFT` produces an enormous page index, causing potentially dangerous page cache operations. 3. **Integer overflow in retrieve**: `outarg->offset + num` can overflow, skipping the cap on `num`, potentially reading beyond file bounds. ### Step 2.4: ASSESS THE FIX QUALITY - **Obviously correct**: Standard overflow prevention patterns (check before add, rearrange subtraction) - **Minimal/surgical**: Only adds validation checks, no behavioral changes for valid inputs - **Regression risk**: Extremely low - only rejects previously-invalid inputs (offset >= MAX_LFS_FILESIZE) or changes arithmetic to prevent overflow - **No red flags**: Single file, well-contained Record: [Fix is obviously correct, minimal, and cannot cause regression for valid FUSE operations] --- ## PHASE 3: GIT HISTORY INVESTIGATION ### Step 3.1: BLAME THE CHANGED LINES - `fuse_notify_store()` core code from commit `a1d75f258230b7` (Miklos Szeredi, 2010-07-12) - "fuse: add store request" - first appeared in **v2.6.36** - `fuse_retrieve()` overflow-prone line from commit `4d53dc99baf139` (Maxim Patlasov, 2012-10-26) - "fuse: rework fuse_retrieve()" - first appeared in **v3.9** - `fuse_notify_retrieve()` from `2d45ba381a74a7` (Miklos Szeredi, 2010-07-12) - "fuse: add retrieve request" - first appeared in **v2.6.36** Record: [Buggy code introduced in v2.6.36 (2010) and v3.9 (2013). Present in ALL active stable trees.] ### Step 3.2: FOLLOW THE FIXES TAG No Fixes: tag present (expected). ### Step 3.3: CHECK FILE HISTORY FOR RELATED CHANGES Recent changes to `fs/fuse/dev.c` include folio conversions, io-uring support, and the related `9d81ba6d49a74` "fuse: Block access to folio overlimit" syzbot fix. The file has 78+ changes since v6.6. The fix is independent of all of these. Record: [Standalone fix, no prerequisites needed] ### Step 3.4: CHECK THE AUTHOR'S OTHER COMMITS Joanne Koong has 12+ commits to `fs/fuse/dev.c`, including the large folio support series. She is a regular and significant FUSE contributor. The fix was reviewed and committed by Miklos Szeredi, the FUSE maintainer. Record: [Author is a major FUSE contributor; patch committed by subsystem maintainer] ### Step 3.5: CHECK FOR DEPENDENT/PREREQUISITE COMMITS The fix only adds new validation checks and rearranges arithmetic. It does not depend on any other commits. The context differs slightly in stable trees (pages vs folios, different error handling style), but the core logic is identical. Record: [No dependencies. Will need minor context adjustments for backport to stable trees using pages instead of folios] --- ## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH ### Step 4.1-4.5: MAILING LIST I was unable to find the specific mailing list thread for this commit on lore.kernel.org (the site is protected by anti-bot measures and the commit may be very recent/not yet indexed). However: - The commit is signed-off by the FUSE maintainer Miklos Szeredi - Joanne Koong is a well-known FUSE contributor - The fix is technically straightforward and self-explanatory Record: [Unable to verify lore discussion due to anti-bot protection. Commit signed by maintainer Miklos Szeredi.] --- ## PHASE 5: CODE SEMANTIC ANALYSIS ### Step 5.1-5.4: CALL CHAIN ANALYSIS The call chain is: ``` fuse_dev_write() / fuse_dev_splice_write() [userspace writes to /dev/fuse] -> fuse_dev_do_write() -> fuse_notify() [when oh.unique == 0, notification message] -> fuse_notify_store() [FUSE_NOTIFY_STORE] -> fuse_notify_retrieve() [FUSE_NOTIFY_RETRIEVE] -> fuse_retrieve() ``` The path is **directly reachable from userspace** - the FUSE server writes to `/dev/fuse` with crafted notification messages. The `outarg` values (offset, size) come directly from this userspace write. ### Step 5.5: SIMILAR PATTERNS Verified that the same three overflow patterns exist in v5.15, v6.1, and v6.6 stable trees at the exact same lines. Record: [Bug is reachable from userspace via /dev/fuse writes. All active stable trees contain the vulnerable code.] --- ## PHASE 6: CROSS-REFERENCING AND STABLE TREE ANALYSIS ### Step 6.1: BUGGY CODE IN STABLE TREES Confirmed the exact buggy patterns exist in: - **v6.6**: lines 1602, 1608, 1684 - **v6.1**: lines 1599, 1605, 1681 - **v5.15**: lines 1591, 1597, 1673 Record: [Bug exists in ALL active stable trees going back to v2.6.36] ### Step 6.2: BACKPORT COMPLICATIONS The file has undergone significant changes (78+ commits since v6.6), primarily folio conversions. The stable trees still use pages. However: - The validation checks (MAX_LFS_FILESIZE) are context-independent - The `num` capping logic is purely arithmetic - The overflow rearrangement in `fuse_retrieve()` is a one-line change The patch will need minor context adjustments (different error handling style with `goto copy_finish` vs `return` in v6.6, and `outarg.size` instead of `num` for the `while` loop). But the core logic applies cleanly. Record: [Minor context conflicts expected. Core fix logic applies unchanged.] ### Step 6.3: RELATED FIXES IN STABLE No prior fixes for this specific integer overflow/bounds checking issue were found. --- ## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT ### Step 7.1: SUBSYSTEM CRITICALITY - **Subsystem**: fs/fuse - filesystems (IMPORTANT) - FUSE is widely used: Docker/containers, virtiofs, SSHFS, Android, embedded systems - Bugs in FUSE notification paths affect all FUSE users ### Step 7.2: SUBSYSTEM ACTIVITY Very active subsystem - 78+ changes since v6.6. The fix addresses bugs present since initial implementation. Record: [FUSE is IMPORTANT subsystem, widely used across containers, VMs, and embedded systems] --- ## PHASE 8: IMPACT AND RISK ASSESSMENT ### Step 8.1: WHO IS AFFECTED All systems using FUSE with notify_store or notify_retrieve functionality. This includes virtiofs (QEMU/KVM VMs), container filesystems, and any FUSE server using cache management notifications. ### Step 8.2: TRIGGER CONDITIONS - Triggered when a FUSE server sends a NOTIFY_STORE or NOTIFY_RETRIEVE with large offset values - Can be triggered by a buggy FUSE server, or a malicious/compromised one - In virtiofs scenarios, the host-side FUSE server could send crafted values ### Step 8.3: FAILURE MODE SEVERITY - **Integer overflow in store**: `end = outarg.offset + outarg.size` wraps to small value -> `fuse_write_update_attr()` called with wrong file_size -> **inode metadata corruption (CRITICAL)** - **Missing MAX_LFS_FILESIZE check**: Enormous page index in `filemap_grab_folio()` -> potential page cache corruption or kernel crash -> **CRITICAL** - **Overflow in retrieve**: `outarg->offset + num` wraps -> num not capped correctly -> potential OOB read -> **HIGH** Record: [Failure modes: data corruption, potential crash. Severity: CRITICAL] ### Step 8.4: RISK-BENEFIT RATIO - **BENEFIT**: HIGH - prevents integer overflow leading to data corruption and potential crashes in a widely-used filesystem subsystem - **RISK**: VERY LOW - adds simple validation checks, ~15 lines, obviously correct, cannot affect valid operations - **Ratio**: Strongly favorable for backporting --- ## PHASE 9: FINAL SYNTHESIS ### Step 9.1: EVIDENCE COMPILATION **FOR backporting:** - Fixes real integer overflow bugs reachable from userspace - Prevents potential data corruption (inode metadata) and OOB access - Bug exists since v2.6.36 (2010) - affects ALL stable trees - Fix is small (~15 lines), surgical, obviously correct - Authored by major FUSE contributor, committed by FUSE maintainer - FUSE is widely deployed (containers, VMs, embedded) - Zero risk of regression for valid operations **AGAINST backporting:** - No explicit Cc: stable or Fixes: tag (expected - that's why we're reviewing) - Minor context adjustments needed for backport (folio vs page) - No syzbot report or explicit bug report mentioned ### Step 9.2: STABLE RULES CHECKLIST 1. Obviously correct and tested? **YES** - standard overflow prevention patterns 2. Fixes a real bug? **YES** - integer overflow on user-controlled data 3. Important issue? **YES** - potential data corruption and OOB access 4. Small and contained? **YES** - ~15 lines in 1 file, 3 functions 5. No new features or APIs? **YES** - pure validation 6. Can apply to stable? **YES** - with minor context adjustments ### Step 9.3: EXCEPTION CATEGORIES Not an exception category - this is a straightforward bug fix. --- ## Verification - [Phase 1] Parsed tags: SOB from Joanne Koong (author) and Miklos Szeredi (maintainer) - [Phase 2] Diff analysis: 3 hunks adding MAX_LFS_FILESIZE bounds checks and overflow prevention - [Phase 2] Confirmed struct types: `fuse_notify_store_out.offset` is uint64_t, `.size` is uint32_t - [Phase 3] git blame: buggy code introduced in a1d75f258230b7 (v2.6.36, 2010) and 4d53dc99baf139 (v3.9, 2013) - [Phase 3] git log --author: Joanne Koong has 12+ commits to fs/fuse/dev.c - [Phase 4] b4 dig: could not match exact commit (not yet in tree). Web search unable to find lore thread. - [Phase 5] Call chain traced: fuse_dev_write() -> fuse_dev_do_write() -> fuse_notify() -> fuse_notify_store()/fuse_notify_retrieve() - directly reachable from userspace /dev/fuse writes - [Phase 6] Confirmed buggy patterns in v5.15 (lines 1591,1597,1673), v6.1 (lines 1599,1605,1681), v6.6 (lines 1602,1608,1684) - [Phase 6] Confirmed MAX_LFS_FILESIZE defined in include/linux/fs.h, used in fs/fuse/inode.c for s_maxbytes - [Phase 7] FUSE is important subsystem, very active (78+ changes since v6.6) - [Phase 8] Failure mode: integer overflow -> data corruption (inode metadata) and potential OOB page cache access; severity CRITICAL - [Phase 8] fuse_write_update_attr() confirmed: sets inode->i_size based on computed `pos` value - overflow leads to wrong i_size - UNVERIFIED: Exact lore.kernel.org discussion thread for this commit (anti-bot protection blocked access) --- The fix addresses real integer overflow and missing bounds checking bugs in FUSE notification store/retrieve paths. The bugs have existed since 2010, affect all stable trees, are reachable from userspace, and can lead to data corruption or out-of-bounds access. The fix is small (~15 lines), obviously correct, authored by a regular contributor, and committed by the subsystem maintainer. The risk-benefit ratio strongly favors backporting. **YES** fs/fuse/dev.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 0b0241f47170d..4a791ff12d009 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1780,7 +1780,11 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size, if (size - sizeof(outarg) != outarg.size) return -EINVAL; + if (outarg.offset >= MAX_LFS_FILESIZE) + return -EINVAL; + nodeid = outarg.nodeid; + num = min(outarg.size, MAX_LFS_FILESIZE - outarg.offset); down_read(&fc->killsb); @@ -1793,13 +1797,12 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size, index = outarg.offset >> PAGE_SHIFT; offset = outarg.offset & ~PAGE_MASK; file_size = i_size_read(inode); - end = outarg.offset + outarg.size; + end = outarg.offset + num; if (end > file_size) { file_size = end; - fuse_write_update_attr(inode, file_size, outarg.size); + fuse_write_update_attr(inode, file_size, num); } - num = outarg.size; while (num) { struct folio *folio; unsigned int folio_offset; @@ -1879,7 +1882,7 @@ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode, num = min(outarg->size, fc->max_write); if (outarg->offset > file_size) num = 0; - else if (outarg->offset + num > file_size) + else if (num > file_size - outarg->offset) num = file_size - outarg->offset; num_pages = (num + offset + PAGE_SIZE - 1) >> PAGE_SHIFT; @@ -1961,6 +1964,9 @@ static int fuse_notify_retrieve(struct fuse_conn *fc, unsigned int size, fuse_copy_finish(cs); + if (outarg.offset >= MAX_LFS_FILESIZE) + return -EINVAL; + down_read(&fc->killsb); err = -ENOENT; nodeid = outarg.nodeid; -- 2.53.0