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 442333B2FFD; Mon, 20 Apr 2026 13:16:45 +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=1776691005; cv=none; b=BFf4gajJmi7JTLCUpBvPkzBi06NGrtcms+Teua1abKG3mrpaimVxWPmILRbLy8uZXVx5ePj7t2XtLPmB93eOJYwGMds0HZGUoxr1HQyGQqOMpbU+9S6rpYkw581ospWrYJZUB6RF7AhvNCOYyKNTaAWlauS//xLv6bHvCTkN7zk= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776691005; c=relaxed/simple; bh=5pHDTXbMS4Kzfdrn4OgyozWPE84oR6Ol6ivx5/GaLEM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=FFJqrNrwHv2z1olbSqVmCfAaGvI0RcqbX2f+MSmQq6zUqafWdvVRu8e8BduahXsDnJgGULFmaZb2ko6D7tFkIUe3d+YtMWaomMZspwhmpcJZRukoHQsSjR6JTINB09GQZD7GCNKlyPanAP7fakyDierf5hkdiG7XNr1W/BL6H1s= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=cjctohkk; 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="cjctohkk" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 7170CC2BCB4; Mon, 20 Apr 2026 13:16:43 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1776691005; bh=5pHDTXbMS4Kzfdrn4OgyozWPE84oR6Ol6ivx5/GaLEM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cjctohkkXg0/xbBQI9/LxAPzwo+xs3Gd5PI2q1v+bNAvyKFk0WlESIu1RgldCntI4 vJrDp1pVOoS4gLUofiR6arl69u874Sc1hygmjrJzxnq7h90mWCZY0BHmDcFoIIW6tW Lkhwx+X1qc4CMEIWmdxozou3s7f9eg7sstPCIz4W/L6buuPCU2f3xpScLv47YDozwQ N5HFBzSy4w5wvFYDCXj0iawTGcYmvxHEm1E/XIykIlc4TX/GOeTf6Ne2P3m2onMFMB 30V4J3oJrvILSh7WhVmhOlJOpEWEQ3bdozOuki+95DW6SO+nzYgeF0lK+9xh1Bv51I tVKwmMBldZ+YA== From: Sasha Levin To: patches@lists.linux.dev, stable@vger.kernel.org Cc: Yun Zhou , syzbot+4c1966e88c28fa96e053@syzkaller.appspotmail.com, Dave Kleikamp , Sasha Levin , shaggy@kernel.org, jfs-discussion@lists.sourceforge.net, linux-kernel@vger.kernel.org Subject: [PATCH AUTOSEL 7.0-5.10] jfs: add dmapctl integrity check to prevent invalid operations Date: Mon, 20 Apr 2026 09:08:24 -0400 Message-ID: <20260420131539.986432-38-sashal@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260420131539.986432-1-sashal@kernel.org> References: <20260420131539.986432-1-sashal@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@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-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From: Yun Zhou [ Upstream commit cce219b203c4b9cb445e910c7090d1f58af847c5 ] Add check_dmapctl() to validate dmapctl structure integrity, focusing on preventing invalid operations caused by on-disk corruption. Key checks: - nleafs bounded by [0, LPERCTL] (maximum leaf nodes per dmapctl). - l2nleafs bounded by [0, L2LPERCTL] and consistent with nleafs (nleafs must be 2^l2nleafs). - leafidx must be exactly CTLLEAFIND (expected leaf index position). - height bounded by [0, L2LPERCTL >> 1] (valid tree height range). - budmin validity: NOFREE only if nleafs=0; otherwise >= BUDMIN. - Leaf nodes fit within stree array (leafidx + nleafs <= CTLTREESIZE). - Leaf node values are either non-negative or NOFREE. Invoked in dbAllocAG(), dbFindCtl(), dbAdjCtl() and dbExtendFS() when accessing dmapctl pages, catching corruption early before dmap operations trigger invalid memory access or logic errors. This fixes the following UBSAN warning. [58245.668090][T14017] ------------[ cut here ]------------ [58245.668103][T14017] UBSAN: shift-out-of-bounds in fs/jfs/jfs_dmap.c:2641:11 [58245.668119][T14017] shift exponent 110 is too large for 32-bit type 'int' [58245.668137][T14017] CPU: 0 UID: 0 PID: 14017 Comm: 4c1966e88c28fa9 Tainted: G E 6.18.0-rc4-00253-g21ce5d4ba045-dirty #124 PREEMPT_{RT,(full)} [58245.668174][T14017] Tainted: [E]=UNSIGNED_MODULE [58245.668176][T14017] Hardware name: QEMU Ubuntu 25.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [58245.668184][T14017] Call Trace: [58245.668200][T14017] [58245.668208][T14017] dump_stack_lvl+0x189/0x250 [58245.668288][T14017] ? __pfx_dump_stack_lvl+0x10/0x10 [58245.668301][T14017] ? __pfx__printk+0x10/0x10 [58245.668315][T14017] ? lock_metapage+0x303/0x400 [jfs] [58245.668406][T14017] ubsan_epilogue+0xa/0x40 [58245.668422][T14017] __ubsan_handle_shift_out_of_bounds+0x386/0x410 [58245.668462][T14017] dbSplit+0x1f8/0x200 [jfs] [58245.668543][T14017] dbAdjCtl+0x34c/0xa20 [jfs] [58245.668628][T14017] dbAllocNear+0x2ee/0x3d0 [jfs] [58245.668710][T14017] dbAlloc+0x933/0xba0 [jfs] [58245.668797][T14017] ea_write+0x374/0xdd0 [jfs] [58245.668888][T14017] ? __pfx_ea_write+0x10/0x10 [jfs] [58245.668966][T14017] ? __jfs_setxattr+0x76e/0x1120 [jfs] [58245.669046][T14017] __jfs_setxattr+0xa01/0x1120 [jfs] [58245.669135][T14017] ? __pfx___jfs_setxattr+0x10/0x10 [jfs] [58245.669216][T14017] ? mutex_lock_nested+0x154/0x1d0 [58245.669252][T14017] ? __jfs_xattr_set+0xb9/0x170 [jfs] [58245.669333][T14017] __jfs_xattr_set+0xda/0x170 [jfs] [58245.669430][T14017] ? __pfx___jfs_xattr_set+0x10/0x10 [jfs] [58245.669509][T14017] ? xattr_full_name+0x6f/0x90 [58245.669546][T14017] ? jfs_xattr_set+0x33/0x60 [jfs] [58245.669636][T14017] ? __pfx_jfs_xattr_set+0x10/0x10 [jfs] [58245.669726][T14017] __vfs_setxattr+0x43c/0x480 [58245.669743][T14017] __vfs_setxattr_noperm+0x12d/0x660 [58245.669756][T14017] vfs_setxattr+0x16b/0x2f0 [58245.669768][T14017] ? __pfx_vfs_setxattr+0x10/0x10 [58245.669782][T14017] filename_setxattr+0x274/0x600 [58245.669795][T14017] ? __pfx_filename_setxattr+0x10/0x10 [58245.669806][T14017] ? getname_flags+0x1e5/0x540 [58245.669829][T14017] path_setxattrat+0x364/0x3a0 [58245.669840][T14017] ? __pfx_path_setxattrat+0x10/0x10 [58245.669859][T14017] ? __se_sys_chdir+0x1b9/0x280 [58245.669876][T14017] __x64_sys_lsetxattr+0xbf/0xe0 [58245.669888][T14017] do_syscall_64+0xfa/0xfa0 [58245.669901][T14017] ? lockdep_hardirqs_on+0x9c/0x150 [58245.669913][T14017] ? entry_SYSCALL_64_after_hwframe+0x77/0x7f [58245.669927][T14017] ? exc_page_fault+0xab/0x100 [58245.669937][T14017] entry_SYSCALL_64_after_hwframe+0x77/0x7f Reported-by: syzbot+4c1966e88c28fa96e053@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=4c1966e88c28fa96e053 Signed-off-by: Yun Zhou Signed-off-by: Dave Kleikamp Signed-off-by: Sasha Levin --- LLM Generated explanations, may be completely bogus: Now let me compile the full analysis. --- ## PHASE 1: COMMIT MESSAGE FORENSICS ### Step 1.1: PARSE THE SUBJECT LINE Record: [jfs] [add integrity check] - Adds `check_dmapctl()` function to validate dmapctl structure integrity to prevent invalid operations from on-disk corruption. ### Step 1.2: PARSE ALL COMMIT MESSAGE TAGS - **Reported-by:** syzbot+4c1966e88c28fa96e053@syzkaller.appspotmail.com (fuzzer-found bug, strong signal) - **Closes:** https://syzkaller.appspot.com/bug?extid=4c1966e88c28fa96e053 - **Signed-off-by:** Yun Zhou (author) - **Signed-off-by:** Dave Kleikamp (JFS maintainer applied it) - No Fixes: tag (expected for autosel candidates) - No Cc: stable (expected for autosel candidates) ### Step 1.3: ANALYZE THE COMMIT BODY TEXT The commit describes a UBSAN shift-out-of-bounds at `fs/jfs/jfs_dmap.c:2641:11` in `dbSplit()` where a corrupt `budmin` value from an on-disk dmapctl structure leads to a shift exponent of 110 (far exceeding the 32-bit limit). The call chain is: `lsetxattr` syscall -> `__jfs_setxattr` -> `ea_write` -> `dbAlloc` -> `dbAllocNear` -> `dbAdjCtl` -> `dbSplit`. Record: Bug is a UBSAN shift-out-of-bounds triggered by corrupt dmapctl on-disk data. Failure mode is undefined behavior from invalid shift. Root cause is lack of comprehensive validation of dmapctl fields read from disk. ### Step 1.4: DETECT HIDDEN BUG FIXES This is not a hidden bug fix - it's an explicitly declared fix for a syzbot-reported UBSAN issue. The commit directly fixes undefined behavior caused by corrupt on-disk data. --- ## PHASE 2: DIFF ANALYSIS - LINE BY LINE ### Step 2.1: INVENTORY THE CHANGES - **File:** `fs/jfs/jfs_dmap.c` only (single file) - **New function:** `check_dmapctl()` (~87 lines added) - **3 replacement sites:** `dbAllocAG()`, `dbFindCtl()`, `dbAdjCtl()` - each replaces `if (dcp->leafidx != cpu_to_le32(CTLLEAFIND))` with `if (unlikely(!check_dmapctl(dcp)))` - **5 new check sites in dbExtendFS():** Adds `check_dmapctl()` calls after reading L2, L1, and L0 dmapctl pages where no checks existed before - Total: ~87 lines for the new function + 8 call sites (3 replacements + 5 additions) = ~110 lines net change ### Step 2.2: UNDERSTAND THE CODE FLOW CHANGE **Before:** Only `leafidx == CTLLEAFIND` was validated when reading dmapctl pages in 3 functions. `budmin`, `nleafs`, `l2nleafs`, `height`, and leaf values were NOT validated. In `dbExtendFS()`, no dmapctl validation at all. **After:** All 7 key fields of dmapctl are validated before any arithmetic is performed on them. This catches corrupt values (like the `budmin` that led to shift exponent 110) before they flow into `BUDSIZE()`, `BLKTOCTLLEAF()`, or array index operations. ### Step 2.3: IDENTIFY THE BUG MECHANISM Category: **Input validation / sanitizer-reported undefined behavior** The `BUDSIZE(s,m)` macro at line 275 of `jfs_dmap.h` is `(1 << ((s) - (m)))`. When `leaf[leafno]` is corrupt (e.g., 115) and `budmin` is 5 (BUDMIN), `cursz = 115 - 1 = 114`, and `BUDSIZE(114, 5) = (1 << 109)` which is shift-out-of-bounds for a 32-bit int. The `check_dmapctl()` validates `budmin >= BUDMIN` and leaf values are within `[NOFREE, 31]`, preventing such overflows. ### Step 2.4: ASSESS THE FIX QUALITY - The fix is well-structured with clear boundary checks against known constants - It replaces weaker checks with comprehensive validation - Each check maps to a specific invariant of the dmapctl structure - Error paths return -EIO which is the existing pattern - The leaf value range check `val > 31` matches the fact that `BUDSIZE(31, 5) = (1 << 26)` is the maximum valid shift - Risk of regression is low - it only makes validation stricter, all valid dmapctl pages will pass --- ## PHASE 3: GIT HISTORY INVESTIGATION ### Step 3.1: BLAME THE CHANGED LINES The 3 existing `leafidx != cpu_to_le32(CTLLEAFIND)` checks being replaced were all introduced in the initial Linux git import (v2.6.12, commit 1da177e4c3f41). This is ancient code present in ALL stable trees. ### Step 3.2: FOLLOW THE FIXES: TAG No Fixes: tag present. The bug is in the fundamental lack of validation of on-disk dmapctl data, which dates back to the initial JFS implementation. ### Step 3.3: CHECK FILE HISTORY FOR RELATED CHANGES JFS has seen a steady stream of similar corruption-defense fixes, all syzbot-driven: - `a5f5e4698f8ab` - fix shift-out-of-bounds in dbSplit (similar bug, budmin < 0) - `d64ff0d230671` - check if leafidx greater than num leaves per dmap tree - `a174706ba4dad` - check to prevent array-index-out-of-bounds in dbAdjTree - Many others addressing the same class of "corrupt on-disk data → UBSAN/crash" This commit is standalone - it does not require other patches in a series. ### Step 3.4: CHECK THE AUTHOR'S OTHER COMMITS Yun Zhou (Wind River) is not a regular JFS contributor. However, the patch was reviewed by Li Lingfeng and applied by Dave Kleikamp (shaggy@kernel.org), who is the JFS maintainer. ### Step 3.5: CHECK FOR DEPENDENT/PREREQUISITE COMMITS The patch replaces `dcp->leafidx != cpu_to_le32(CTLLEAFIND)` checks that have existed since v2.6.12. The code context (function signatures, data structures) is stable and unchanged. No dependencies on recent commits. --- ## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH ### Step 4.1: FIND THE ORIGINAL PATCH DISCUSSION - b4 dig found the match: https://patch.msgid.link/20251128155150.149398 6-1-yun.zhou@windriver.com - This is v2 of the patch (v1 was submitted 2025/11/20, v2 on 2025/11/28) - Dave Kleikamp (JFS maintainer) replied: "This is finally tested and applied." (2026/03/16) - Li Lingfeng reviewed and pinged twice for application ### Step 4.2: CHECK WHO REVIEWED THE PATCH The JFS maintainer (Dave Kleikamp/shaggy) directly tested and applied. Li Lingfeng reviewed. ### Step 4.3: SEARCH FOR THE BUG REPORT The syzbot page confirms: - Bug is reproducible with a C reproducer - First crash: 354 days ago, last: 36 days ago (persistent bug) - The bug has **similar bugs** on linux-6.1 and linux-5.15 that are **NOT patched** (0/3 patched on 6.1, 0/3 on 5.15) - This confirms the bug exists and is unpatched in stable trees ### Step 4.4-4.5: Related patches This is a standalone patch. No explicit stable discussion found, but the syzbot similar bugs table shows the same class of issue exists in stable trees (linux-6.1, linux-5.15) and is NOT yet fixed there. --- ## PHASE 5: CODE SEMANTIC ANALYSIS ### Step 5.1-5.2: KEY FUNCTIONS AND CALLERS The `check_dmapctl()` is called in: 1. **`dbAllocAG()`** - called from `dbAlloc()`, the primary block allocation path 2. **`dbFindCtl()`** - called from `dbAllocCtl()` and `dbAllocAG()` 3. **`dbAdjCtl()`** - called from `dbFreeBits()`, `dbAllocDmap()`, `dbAllocDmapBU()`, and recursively 4. **`dbExtendFS()`** - called during filesystem extension ### Step 5.3-5.4: CALL CHAIN (reachability) The syzbot crash trace confirms reachability from userspace: `lsetxattr` (syscall) → `__jfs_setxattr` → `ea_write` → `dbAlloc` → `dbAllocNear` → `dbAdjCtl` → `dbSplit` This is triggered by a standard `setxattr` syscall on a JFS filesystem. Any unprivileged user who can write to a JFS filesystem can trigger this. --- ## PHASE 6: CROSS-REFERENCING AND STABLE TREE ANALYSIS ### Step 6.1: DOES THE BUGGY CODE EXIST IN STABLE? Yes. The code being modified (the leafidx checks in dbAllocAG, dbFindCtl, dbAdjCtl) has existed since v2.6.12. The bug exists in ALL stable trees. Syzbot confirms it reproduces on linux-6.1 and linux-5.15. ### Step 6.2: CHECK FOR BACKPORT COMPLICATIONS The patch should apply cleanly to most stable trees. The three replacement sites have had the same `dcp->leafidx != cpu_to_le32(CTLLEAFIND)` check since the initial git import. The `dbExtendFS()` additions are to code that is also largely unchanged. There may be minor context conflicts depending on surrounding changes in each stable tree (the many JFS syzbot fixes), but the core code structure is the same. ### Step 6.3: RELATED FIXES IN STABLE The earlier fix `a5f5e4698f8ab` ("jfs: fix shift-out-of-bounds in dbSplit") addressed the same syzbot bug family (#2) by checking `dp->tree.budmin < 0` in `dbAllocCtl`. This commit provides a more comprehensive fix at the dmapctl level, catching corruption earlier and more broadly. --- ## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT ### Step 7.1: SUBSYSTEM CRITICALITY - **Subsystem:** fs/jfs (JFS filesystem) - **Criticality:** IMPORTANT - JFS is a mature filesystem used in enterprise environments, particularly on older IBM/AIX-heritage systems and some embedded environments - The fix is in the block allocation map code, which is core to filesystem operations ### Step 7.2: SUBSYSTEM ACTIVITY JFS is in maintenance mode - no new features, but receives a steady stream of corruption-defense fixes (mostly syzbot-driven). This is exactly the kind of fix that goes to stable. --- ## PHASE 8: IMPACT AND RISK ASSESSMENT ### Step 8.1: WHO IS AFFECTED All users of JFS filesystems. Trigger requires a corrupt JFS filesystem image (could be from disk corruption, or crafted by an attacker with local access). ### Step 8.2: TRIGGER CONDITIONS - Requires mounting or using a JFS filesystem with corrupt dmapctl pages - Syzbot reproduces this via `lsetxattr` syscall on a crafted image - Can be triggered by unprivileged user operations on a mounted JFS filesystem - Corrupt filesystem images can come from disk errors, USB drives, or malicious crafting ### Step 8.3: FAILURE MODE SEVERITY - **UBSAN shift-out-of-bounds** = undefined behavior - Without UBSAN, this could lead to incorrect memory access patterns, potential memory corruption - The corrupt budmin/leaf values flow into array indexing and shift operations - Severity: **HIGH** (undefined behavior, potential for memory corruption or crash) ### Step 8.4: RISK-BENEFIT RATIO - **BENEFIT:** High - fixes a reproducible syzbot bug, prevents undefined behavior from corrupt on-disk data, protects against a class of bugs (not just one instance) - **RISK:** Low-Medium - the fix is ~87 lines of new validation code (moderate size), but all checks are straightforward bounds checks against well-defined constants. The existing error return pattern is preserved. The fix only adds stricter validation - it cannot break valid filesystems. - **Ratio:** Favorable for backport --- ## PHASE 9: FINAL SYNTHESIS ### Step 9.1: COMPILE THE EVIDENCE **Evidence FOR backporting:** - Fixes a real, reproducible syzbot bug (UBSAN shift-out-of-bounds in dbSplit) - Syzbot confirms the same bug exists on linux-6.1 and linux-5.15 (unpatched) - Protects against undefined behavior from corrupt on-disk data - Applied and tested by the JFS maintainer (Dave Kleikamp) - Standalone patch, no dependencies - Code being modified has existed since v2.6.12 (present in all stable trees) - Bug is triggerable from unprivileged userspace syscalls - Pattern is consistent with dozens of similar JFS fixes that go to stable **Evidence AGAINST backporting:** - Somewhat large (~110 lines of net change) for a stable fix - Adds a new function rather than a minimal surgical fix - Could theoretically reject valid dmapctl pages if the checks are too strict (but checks match documented invariants) - The `dbExtendFS()` additions are new check sites (not replacements), adding code to paths that had none before ### Step 9.2: STABLE RULES CHECKLIST 1. **Obviously correct and tested?** YES - maintainer tested and applied; checks are against documented constants 2. **Fixes a real bug?** YES - syzbot-reported UBSAN, reproducible with C reproducer 3. **Important issue?** YES - undefined behavior (shift-out-of-bounds), potential memory corruption 4. **Small and contained?** BORDERLINE - ~110 lines but all in one file, one new function with clear purpose 5. **No new features or APIs?** YES - purely defensive validation 6. **Can apply to stable?** YES - code structure is stable across all trees ### Step 9.3: EXCEPTION CATEGORIES Not an exception category, but fits the standard "fixes real bug found by fuzzer" pattern. ### Step 9.4: DECISION The fix addresses a real, reproducible syzbot bug that exists in all stable trees. While the patch is somewhat larger than ideal for stable (adding a complete validation function), it is well-contained within a single file and function, all checks are obviously correct bounds checks, and it was tested by the subsystem maintainer. The undefined behavior it prevents could lead to memory corruption or crashes on corrupt JFS images. The syzbot data shows similar bugs are actively reproducing on stable kernels (linux-6.1, linux-5.15). The benefit clearly outweighs the moderate risk. --- ## Verification - [Phase 1] Parsed tags: Reported-by syzbot+4c1966e88c28fa96e053, Closes: syzkaller link, SOBs from author + JFS maintainer - [Phase 2] Diff analysis: ~87 lines new validation function, 3 replacement sites, 5 new check sites, all in `fs/jfs/jfs_dmap.c` - [Phase 2] Root cause: `BUDSIZE(cursz, budmin)` macro does `1 << (s - m)`, corrupt leaf value (e.g. 115) → shift exponent 109 - [Phase 3] git blame: All 3 existing leafidx checks date to `1da177e4c3f41` (v2.6.12, 2005), present in all stable trees - [Phase 3] git log: File has extensive syzbot-driven fix history (20+ commits addressing similar corruption issues) - [Phase 3] Author: Yun Zhou (Wind River), not regular JFS contributor but patch applied by maintainer Dave Kleikamp - [Phase 4] b4 dig: Found match at https://patch.msgid.link/202511281551 50.1493986-1-yun.zhou@windriver.com - [Phase 4] b4 dig -a: v1 (2025/11/20), v2 (2025/11/28) - committed version is v2 (latest) - [Phase 4] b4 dig -w: JFS maintainer (shaggy@kernel.org) was included, tested and applied - [Phase 4] syzbot page: Bug first crash 354d ago, last 36d ago; reproducer available; similar bugs on linux-6.1 (0/3 patched) and linux-5.15 (0/3 patched) - [Phase 4] Maintainer comment: "This is finally tested and applied" (Dave Kleikamp, 2026/03/16) - [Phase 5] Call chain verified from syzbot trace: lsetxattr → __jfs_setxattr → ea_write → dbAlloc → dbAllocNear → dbAdjCtl → dbSplit (userspace reachable) - [Phase 6] Buggy code present in all stable trees (since v2.6.12) - [Phase 6] Backport: should apply cleanly, no significant structural changes in the 3 replacement sites - [Phase 8] Severity: HIGH (undefined behavior from shift-out-of-bounds, potential memory corruption) - [Phase 8] Trigger: unprivileged user setxattr on corrupt JFS filesystem **YES** fs/jfs/jfs_dmap.c | 114 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 3 deletions(-) diff --git a/fs/jfs/jfs_dmap.c b/fs/jfs/jfs_dmap.c index 2abe8cc02ee6f..a841cf21da7de 100644 --- a/fs/jfs/jfs_dmap.c +++ b/fs/jfs/jfs_dmap.c @@ -133,6 +133,93 @@ static const s8 budtab[256] = { 2, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -1 }; +/* + * check_dmapctl - Validate integrity of a dmapctl structure + * @dcp: Pointer to the dmapctl structure to check + * + * Return: true if valid, false if corrupted + */ +static bool check_dmapctl(struct dmapctl *dcp) +{ + s8 budmin = dcp->budmin; + u32 nleafs, l2nleafs, leafidx, height; + int i; + + nleafs = le32_to_cpu(dcp->nleafs); + /* Check basic field ranges */ + if (unlikely(nleafs > LPERCTL)) { + jfs_err("dmapctl: invalid nleafs %u (max %u)", + nleafs, LPERCTL); + return false; + } + + l2nleafs = le32_to_cpu(dcp->l2nleafs); + if (unlikely(l2nleafs > L2LPERCTL)) { + jfs_err("dmapctl: invalid l2nleafs %u (max %u)", + l2nleafs, L2LPERCTL); + return false; + } + + /* Verify nleafs matches l2nleafs (must be power of two) */ + if (unlikely((1U << l2nleafs) != nleafs)) { + jfs_err("dmapctl: nleafs %u != 2^%u", + nleafs, l2nleafs); + return false; + } + + leafidx = le32_to_cpu(dcp->leafidx); + /* Check leaf index matches expected position */ + if (unlikely(leafidx != CTLLEAFIND)) { + jfs_err("dmapctl: invalid leafidx %u (expected %u)", + leafidx, CTLLEAFIND); + return false; + } + + height = le32_to_cpu(dcp->height); + /* Check tree height is within valid range */ + if (unlikely(height > (L2LPERCTL >> 1))) { + jfs_err("dmapctl: invalid height %u (max %u)", + height, L2LPERCTL >> 1); + return false; + } + + /* Check budmin is valid (cannot be NOFREE for non-empty tree) */ + if (budmin == NOFREE) { + if (unlikely(nleafs > 0)) { + jfs_err("dmapctl: budmin is NOFREE but nleafs %u", + nleafs); + return false; + } + } else if (unlikely(budmin < BUDMIN)) { + jfs_err("dmapctl: invalid budmin %d (min %d)", + budmin, BUDMIN); + return false; + } + + /* Check leaf nodes fit within stree array */ + if (unlikely(leafidx + nleafs > CTLTREESIZE)) { + jfs_err("dmapctl: leaf range exceeds stree size (end %u > %u)", + leafidx + nleafs, CTLTREESIZE); + return false; + } + + /* Check leaf nodes have valid values */ + for (i = leafidx; i < leafidx + nleafs; i++) { + s8 val = dcp->stree[i]; + + if (unlikely(val < NOFREE)) { + jfs_err("dmapctl: invalid leaf value %d at index %d", + val, i); + return false; + } else if (unlikely(val > 31)) { + jfs_err("dmapctl: leaf value %d too large at index %d", val, i); + return false; + } + } + + return true; +} + /* * NAME: dbMount() * @@ -1372,7 +1459,7 @@ dbAllocAG(struct bmap * bmp, int agno, s64 nblocks, int l2nb, s64 * results) dcp = (struct dmapctl *) mp->data; budmin = dcp->budmin; - if (dcp->leafidx != cpu_to_le32(CTLLEAFIND)) { + if (unlikely(!check_dmapctl(dcp))) { jfs_error(bmp->db_ipbmap->i_sb, "Corrupt dmapctl page\n"); release_metapage(mp); return -EIO; @@ -1702,7 +1789,7 @@ static int dbFindCtl(struct bmap * bmp, int l2nb, int level, s64 * blkno) dcp = (struct dmapctl *) mp->data; budmin = dcp->budmin; - if (dcp->leafidx != cpu_to_le32(CTLLEAFIND)) { + if (unlikely(!check_dmapctl(dcp))) { jfs_error(bmp->db_ipbmap->i_sb, "Corrupt dmapctl page\n"); release_metapage(mp); @@ -2485,7 +2572,7 @@ dbAdjCtl(struct bmap * bmp, s64 blkno, int newval, int alloc, int level) return -EIO; dcp = (struct dmapctl *) mp->data; - if (dcp->leafidx != cpu_to_le32(CTLLEAFIND)) { + if (unlikely(!check_dmapctl(dcp))) { jfs_error(bmp->db_ipbmap->i_sb, "Corrupt dmapctl page\n"); release_metapage(mp); return -EIO; @@ -3454,6 +3541,11 @@ int dbExtendFS(struct inode *ipbmap, s64 blkno, s64 nblocks) return -EIO; } l2dcp = (struct dmapctl *) l2mp->data; + if (unlikely(!check_dmapctl(l2dcp))) { + jfs_error(ipbmap->i_sb, "Corrupt dmapctl page\n"); + release_metapage(l2mp); + return -EIO; + } /* compute start L1 */ k = blkno >> L2MAXL1SIZE; @@ -3471,6 +3563,10 @@ int dbExtendFS(struct inode *ipbmap, s64 blkno, s64 nblocks) if (l1mp == NULL) goto errout; l1dcp = (struct dmapctl *) l1mp->data; + if (unlikely(!check_dmapctl(l1dcp))) { + jfs_error(ipbmap->i_sb, "Corrupt dmapctl page\n"); + goto errout; + } /* compute start L0 */ j = (blkno & (MAXL1SIZE - 1)) >> L2MAXL0SIZE; @@ -3484,6 +3580,10 @@ int dbExtendFS(struct inode *ipbmap, s64 blkno, s64 nblocks) goto errout; l1dcp = (struct dmapctl *) l1mp->data; + if (unlikely(!check_dmapctl(l1dcp))) { + jfs_error(ipbmap->i_sb, "Corrupt dmapctl page\n"); + goto errout; + } /* compute start L0 */ j = 0; @@ -3503,6 +3603,10 @@ int dbExtendFS(struct inode *ipbmap, s64 blkno, s64 nblocks) if (l0mp == NULL) goto errout; l0dcp = (struct dmapctl *) l0mp->data; + if (unlikely(!check_dmapctl(l0dcp))) { + jfs_error(ipbmap->i_sb, "Corrupt dmapctl page\n"); + goto errout; + } /* compute start dmap */ i = (blkno & (MAXL0SIZE - 1)) >> @@ -3518,6 +3622,10 @@ int dbExtendFS(struct inode *ipbmap, s64 blkno, s64 nblocks) goto errout; l0dcp = (struct dmapctl *) l0mp->data; + if (unlikely(!check_dmapctl(l0dcp))) { + jfs_error(ipbmap->i_sb, "Corrupt dmapctl page\n"); + goto errout; + } /* compute start dmap */ i = 0; -- 2.53.0