From: Greg Kroah-Hartman Date: Mon, 20 Jul 2026 14:35:30 +0000 (+0200) Subject: 7.1-stable patches X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3d459ad5e2192df3d06a97bc33483fb913fcd1a7;p=thirdparty%2Fkernel%2Fstable-queue.git 7.1-stable patches added patches: 9p-skip-nlink-update-in-cacheless-mode-to-fix-warn_on.patch fs-ntfs3-add-depth-limit-to-indx_find_buffer-to-prevent-stack-overflow.patch fs-ntfs3-bound-attr_off-in-updateresidentvalue-against-data_off.patch fs-ntfs3-bound-copy_lcns-dp-page_lcns-index-in-analysis-pass.patch fs-ntfs3-bound-deleteindexentryallocation-memmove-length.patch fs-ntfs3-bound-ntfs_de-view.data_off-in-updaterecorddata-root-allocation.patch fs-ntfs3-fix-syncing-wrong-inode-on-dirsync-cross-directory-rename.patch fs-ntfs3-validate-lcns_follow-in-log_replay-conversion.patch fs-proc-fix-kpf_ksm-reported-for-all-anonymous-pages.patch fs-proc-task_mmu-do-not-warn-on-seeing-non-migration-pmd-entry.patch fs-proc-task_mmu-fix-hugetlb-self-deadlock-in-pagemap_scan_pte_hole.patch fs-proc-task_mmu-fix-make_uffd_wp_huge_pte-prot-update-race.patch fs-proc-task_mmu-use-huge_page_size-in-pagemap_scan_hugetlb_entry.patch kcov-use-write_once-for-selftest-mode-stores.patch kho-make-sure-scratch-size-is-always-aligned-by-cma_min_alignment_bytes.patch landlock-fix-landlock_scope_signal-bypass-on-the-sigio-path.patch mips-dec-ensure-32-bit-stack-location-for-o32-prom_printf.patch mips-ip22-gio-fix-device-reference-leak-in-probe.patch mips-ip22-gio-fix-gio-device-memory-leak.patch mips-ip22-gio-fix-kfree-of-static-object.patch mips-sched-fix-cpumask_offstack-memory-corruption.patch mm-damon-core-always-put-unsuccessfully-committed-target-pids.patch mm-damon-core-make-charge_addr_from-aware-of-end-address-exclusivity.patch mm-damon-sysfs-schemes-fix-dir-put-orders-in-access_pattern_add_dirs.patch mm-damon-sysfs-schemes-put-stats-for-scheme_add_dirs-internal-error.patch mm-huge_memory-preserve-pmd_swp_uffd_wp-on-device-private-pmd-downgrade.patch mm-hugetlb-fix-hugetlb-cgroup-rsvd-charge-uncharge-mismatch.patch mm-memory_hotplug-fix-incorrect-altmap-passing-in-error-path.patch mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch mm-mm_init-fix-uninitialized-struct-pages-for-zone_device.patch mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch mtd-rawnand-fix-condition-in-nand_select_target.patch mtd-rawnand-pl353-fix-probe-resource-allocation.patch mtd-slram-remove-failed-entries-from-the-device-list.patch net-9p-fix-infinite-loop-in-p9_client_rpc-on-fatal-signal.patch ntfs-add-bounds-check-before-accessing-ea-entries.patch ntfs-add-wq_percpu-to-alloc_workqueue-users.patch ntfs-avoid-heap-allocation-for-free-cluster-readahead-state.patch ntfs-avoid-self-deadlock-during-inode-eviction.patch ntfs-centalize-index_root-header-validation.patch ntfs-detect-mapping-pairs-lcn-accumulator-overflow.patch ntfs-do-not-replace-volume-name-after-lookup-errors.patch ntfs-fail-attrlist-updates-when-the-superblock-is-inactive.patch ntfs-fix-hole-runlist-memory-leak-in-insert-range-error-path.patch ntfs-fix-incorrect-size-of-symbolic-link.patch ntfs-fix-mrec_lock-abba-deadlock-in-rename.patch ntfs-fix-off-by-one-in-mapping-pairs-decoding-bounds-checks.patch ntfs-fix-warn_on-for-resident-attribute-in-ntfs_map_runlist_nolock.patch ntfs-free-volume-wide-resources-on-fill_super-failure.patch ntfs-grow-index-root-value-before-reparent-header-update.patch ntfs-make-system-files-immutable-to-prevent-corruption.patch ntfs-not-change-0-byte-data-attribute-to-non-resident.patch ntfs-only-alias-volume-upcase-to-default-on-exact-match.patch ntfs-reinit-search-context-before-volume-information-lookup.patch ntfs-reject-non-resident-records-for-resident-only-attributes.patch ntfs-sanitize-mft-references-returned-from-ntfs_lookup_inode_by_name.patch ntfs-skip-extent-mft-records-in-writeback-to-prevent-deadlock.patch ntfs-update-index-root-allocated-size-before-shrink.patch ntfs-validate-attribute-values-on-lookup.patch ntfs-validate-index-block-header-more-strictly.patch ntfs-validate-index-entries-on-reading.patch ntfs-validate-resident-index-root-values-on-lookup.patch ntfs-validate-resident-volume-name-values-on-lookup.patch ntfs3-bound-to_move-in-indx_insert_into_root-before-hdr_insert_head.patch ntfs3-cap-restart_table-free-chain-walker-at-rt-used.patch ntfs3-fix-out-of-bounds-read-in-decompress_lznt.patch ntfs3-validate-split-point-offset-in-indx_insert_into_buffer.patch ocfs2-add-journal-null-check-in-ocfs2_checkpoint_inode.patch ocfs2-avoid-moving-extents-to-occupied-clusters.patch ocfs2-fix-null-h_transaction-deref-in-ocfs2_assure_trans_credits.patch ocfs2-fix-ubsan-array-index-out-of-bounds-in-ocfs2_sum_rightmost_rec.patch ocfs2-reject-dinodes-whose-i_rdev-disagrees-with-the-file-type.patch ocfs2-reject-dinodes-with-non-canonical-i_mode-type.patch ocfs2-reject-non-inline-dinodes-with-i_size-and-zero-i_clusters.patch ocfs2-use-kzalloc-for-quota-recovery-bitmap-allocation.patch openrisc-add-full-instruction-cache-invalidate-functions.patch power-supply-bq257xx-fix-vsysmin-clamping-logic.patch power-supply-charger-manager-fix-refcount-leak-in-is_full_charged.patch power-supply-cpcap-battery-fix-missing-nvmem_device_put-causing-reference-leak.patch power-supply-max17042-fix-of-node-reference-imbalance.patch powerpc-dt_cpu_ftrs-set-cpu_ftr_p11_pvr-for-power11-and-later-processors.patch proc-only-bump-parent-nlink-when-registering-directories.patch remoteproc-qcom-fix-leak-when-custom-dump_segments-addition-fails.patch remoteproc-xlnx-check-remote-core-state.patch riscv-cacheinfo-fix-node-reference-leak-in-populate_cache_leaves.patch scsi-sas-skip-opt_sectors-when-dma-reports-no-real-optimization-hint.patch scsi-smartpqi-use-shost_to_hba-in-pqi_scan_finished.patch selftests-landlock-test-scope_signal-on-the-sigio-fowner-pgid-path.patch --- diff --git a/queue-7.1/9p-skip-nlink-update-in-cacheless-mode-to-fix-warn_on.patch b/queue-7.1/9p-skip-nlink-update-in-cacheless-mode-to-fix-warn_on.patch new file mode 100644 index 0000000000..eefeca7210 --- /dev/null +++ b/queue-7.1/9p-skip-nlink-update-in-cacheless-mode-to-fix-warn_on.patch @@ -0,0 +1,71 @@ +From 574aa0b4799470ac814479f1138d19efe6262255 Mon Sep 17 00:00:00 2001 +From: Breno Leitao +Date: Tue, 21 Apr 2026 02:41:09 -0700 +Subject: 9p: skip nlink update in cacheless mode to fix WARN_ON + +From: Breno Leitao + +commit 574aa0b4799470ac814479f1138d19efe6262255 upstream. + +v9fs_dec_count() unconditionally calls drop_nlink() on regular files, +even when the inode's nlink is already zero. In cacheless mode the +client refetches inode metadata from the server (the source of truth) +on every operation, so by the time v9fs_remove() returns, the locally +cached nlink may already reflect the post-unlink value: + + 1. Client initiates unlink, server processes it and sets nlink to 0 + 2. Client refetches inode metadata (nlink=0) before unlink returns + 3. Client's v9fs_remove() completes successfully + 4. Client calls v9fs_dec_count() which calls drop_nlink() on nlink=0 + +This race is easily triggered under heavy unlink workloads, such as +stress-ng's unlink stressor, producing the following warning: + + WARNING: fs/inode.c:417 at drop_nlink+0x4c/0xc8 + Call trace: + drop_nlink+0x4c/0xc8 + v9fs_remove+0x1e0/0x250 [9p] + v9fs_vfs_unlink+0x20/0x38 [9p] + vfs_unlink+0x13c/0x258 + ... + +In cacheless mode the server is authoritative and the inode is on its +way out, so locally adjusting nlink buys nothing. Skip v9fs_dec_count() +entirely when neither CACHE_META nor CACHE_LOOSE is set, which both +avoids the warning and removes a class of nlink races (two concurrent +unlinkers observing nlink > 0 and both calling drop_nlink()) that an +nlink == 0 guard alone would only narrow rather than close. + +Fixes: ac89b2ef9b55 ("9p: don't maintain dir i_nlink if the exported fs doesn't either") +Cc: stable@vger.kernel.org +Suggested-by: Dominique Martinet +Signed-off-by: Breno Leitao +Message-ID: <20260421-9p-v2-1-48762d294fad@debian.org> +Signed-off-by: Dominique Martinet +Signed-off-by: Greg Kroah-Hartman +--- + fs/9p/vfs_inode.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +--- a/fs/9p/vfs_inode.c ++++ b/fs/9p/vfs_inode.c +@@ -488,10 +488,19 @@ static int v9fs_at_to_dotl_flags(int fla + * - ext4 (with dir_nlink feature enabled) sets nlink to 1 if a dir has more + * than EXT4_LINK_MAX (65000) links. + * ++ * In cacheless mode the server is the source of truth for nlink and the ++ * inode is going away immediately, so locally adjusting i_nlink buys ++ * nothing and races with concurrent metadata fetches that may already ++ * have observed the post-unlink value (nlink == 0). ++ * + * @inode: inode whose nlink is being dropped + */ + static void v9fs_dec_count(struct inode *inode) + { ++ struct v9fs_session_info *v9ses = v9fs_inode2v9ses(inode); ++ ++ if (!(v9ses->cache & (CACHE_META | CACHE_LOOSE))) ++ return; + if (!S_ISDIR(inode->i_mode) || inode->i_nlink > 2) + drop_nlink(inode); + } diff --git a/queue-7.1/fs-ntfs3-add-depth-limit-to-indx_find_buffer-to-prevent-stack-overflow.patch b/queue-7.1/fs-ntfs3-add-depth-limit-to-indx_find_buffer-to-prevent-stack-overflow.patch new file mode 100644 index 0000000000..08244a82d2 --- /dev/null +++ b/queue-7.1/fs-ntfs3-add-depth-limit-to-indx_find_buffer-to-prevent-stack-overflow.patch @@ -0,0 +1,81 @@ +From 1ebd684b8f627f75bc3e03f8b2ad8400fd1f02cd Mon Sep 17 00:00:00 2001 +From: Michael Bommarito +Date: Mon, 13 Apr 2026 09:31:17 -0400 +Subject: fs/ntfs3: add depth limit to indx_find_buffer to prevent stack overflow + +From: Michael Bommarito + +commit 1ebd684b8f627f75bc3e03f8b2ad8400fd1f02cd upstream. + +indx_find_buffer() recursively descends the B+ tree index with no depth +limit. A crafted NTFS image with circular index node references causes +unbounded recursion, overflowing the kernel stack and panicking the +system. + +This is reachable by mounting a malicious NTFS filesystem (e.g. from a +USB drive via desktop automount) and deleting a file whose index entry +triggers the rebalancing fallback path in indx_delete_entry(). + +Add a depth parameter and bail out with -EINVAL when it reaches the +fnd->nodes array bound, matching the constraint already enforced by +fnd_push() in indx_find(). + +The related function indx_find() was previously patched for a similar +infinite-loop issue (commit 1732053c8a6b), but indx_find_buffer() was +missed. + +Fixes: 82cae269cfa9 ("fs/ntfs3: Add initialization of super block") +Cc: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-6 +Assisted-by: Codex:gpt-5-4 +Signed-off-by: Michael Bommarito +Signed-off-by: Konstantin Komarov +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs3/index.c | 15 ++++++++++++--- + 1 file changed, 12 insertions(+), 3 deletions(-) + +--- a/fs/ntfs3/index.c ++++ b/fs/ntfs3/index.c +@@ -2022,13 +2022,21 @@ out1: + static struct indx_node *indx_find_buffer(struct ntfs_index *indx, + struct ntfs_inode *ni, + const struct INDEX_ROOT *root, +- __le64 vbn, struct indx_node *n) ++ __le64 vbn, struct indx_node *n, ++ int depth) + { + int err; + const struct NTFS_DE *e; + struct indx_node *r; + const struct INDEX_HDR *hdr = n ? &n->index->ihdr : &root->ihdr; + ++ /* ++ * Limit recursion depth to prevent stack overflow from crafted ++ * images. Use the same bound as the fnd->nodes array (20). ++ */ ++ if (depth > ARRAY_SIZE(((struct ntfs_fnd *)NULL)->nodes)) ++ return ERR_PTR(-EINVAL); ++ + /* Step 1: Scan one level. */ + for (e = hdr_first_de(hdr);; e = hdr_next_de(hdr, e)) { + if (!e) +@@ -2049,7 +2057,8 @@ static struct indx_node *indx_find_buffe + if (err) + return ERR_PTR(err); + +- r = indx_find_buffer(indx, ni, root, vbn, n); ++ r = indx_find_buffer(indx, ni, root, vbn, n, ++ depth + 1); + if (r) + return r; + } +@@ -2462,7 +2471,7 @@ int indx_delete_entry(struct ntfs_index + + fnd_clear(fnd); + +- in = indx_find_buffer(indx, ni, root, sub_vbn, NULL); ++ in = indx_find_buffer(indx, ni, root, sub_vbn, NULL, 0); + if (IS_ERR(in)) { + err = PTR_ERR(in); + goto out; diff --git a/queue-7.1/fs-ntfs3-bound-attr_off-in-updateresidentvalue-against-data_off.patch b/queue-7.1/fs-ntfs3-bound-attr_off-in-updateresidentvalue-against-data_off.patch new file mode 100644 index 0000000000..32ca99b7f0 --- /dev/null +++ b/queue-7.1/fs-ntfs3-bound-attr_off-in-updateresidentvalue-against-data_off.patch @@ -0,0 +1,64 @@ +From d1570c48f49a693974d000251030370ee2e83539 Mon Sep 17 00:00:00 2001 +From: Konstantin Komarov +Date: Tue, 2 Jun 2026 15:15:47 +0200 +Subject: fs/ntfs3: bound attr_off in UpdateResidentValue against data_off + +From: Konstantin Komarov + +commit d1570c48f49a693974d000251030370ee2e83539 upstream. + +In do_action()'s UpdateResidentValue case (fslog.c:3307), +lrh->attr_off and lrh->redo_len come from the on-disk LRH. +When they satisfy aoff + dlen < attr->res.data_off, the +assignment + + attr->res.data_size = cpu_to_le32(aoff + dlen - data_off); + +underflows to ~4 GiB (e.g. 0xFFFFFFF9 when aoff=0x10, dlen=1, +data_off=0x18). Subsequent code that reads attr->res.data_size +to walk the resident attribute payload would then read up to +4 GiB past the 1024-byte MFT record allocation. + +The existing mi_enum_attr() defense in fs/ntfs3/record.c:287 +catches the corrupted data_size on the next attribute walk +and fails the mount, but only on the path that walks all +attributes. A read site that picks an attribute by name and +reads its data_size without re-validating is not covered. +Validate aoff against data_off and asize at the source. + +Reproduced under UML+KASAN on mainline 8d90b09e6741 via +pr_warn-only probe: with aoff=0x10 and data_off=0x18, the +post-assignment data_size is 0xfffffff9 (mount then fails +at -22 from mi_enum_attr). + +Fixes: b46acd6a6a62 ("fs/ntfs3: Add NTFS journal") +Cc: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-7 +Signed-off-by: Michael Bommarito +[almaz.alexandrovich@paragon-software.com: clang-formatted the changes] +Signed-off-by: Konstantin Komarov +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs3/fslog.c | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +--- a/fs/ntfs3/fslog.c ++++ b/fs/ntfs3/fslog.c +@@ -3325,6 +3325,17 @@ skip_load_parent: + nsize = ALIGN(nsize, 8); + data_off = le16_to_cpu(attr->res.data_off); + ++ /* ++ * aoff comes from the on-disk lrh->attr_off. Forbid ++ * writes that begin below the resident attribute's ++ * data_off (which would overwrite the resident header), ++ * and forbid aoff + dlen < data_off, which would make ++ * the data_size assignment below underflow to ~4 GiB. ++ */ ++ if (aoff < data_off || aoff + dlen < data_off || ++ aoff + dlen > asize) ++ goto dirty_vol; ++ + if (nsize < asize) { + memmove(Add2Ptr(attr, aoff), data, dlen); + data = NULL; // To skip below memmove(). diff --git a/queue-7.1/fs-ntfs3-bound-copy_lcns-dp-page_lcns-index-in-analysis-pass.patch b/queue-7.1/fs-ntfs3-bound-copy_lcns-dp-page_lcns-index-in-analysis-pass.patch new file mode 100644 index 0000000000..25614b7f17 --- /dev/null +++ b/queue-7.1/fs-ntfs3-bound-copy_lcns-dp-page_lcns-index-in-analysis-pass.patch @@ -0,0 +1,116 @@ +From 5e7b598660cfa8e5af172cf4c65cffc126333307 Mon Sep 17 00:00:00 2001 +From: Michael Bommarito +Date: Fri, 15 May 2026 12:34:05 -0400 +Subject: fs/ntfs3: bound copy_lcns dp->page_lcns[] index in analysis pass + +From: Michael Bommarito + +commit 5e7b598660cfa8e5af172cf4c65cffc126333307 upstream. + +In log_replay()'s analysis pass, after find_dp() returns a +valid DIR_PAGE_ENTRY for the (target_attr, target_vcn) tuple, +the copy_lcns block walks lrh->lcns_follow further entries: + + t16 = le16_to_cpu(lrh->lcns_follow); + for (i = 0; i < t16; i++) { + size_t j = (size_t)(le64_to_cpu(lrh->target_vcn) - + le64_to_cpu(dp->vcn)); + dp->page_lcns[j + i] = lrh->page_lcns[i]; + } + +find_dp() only validates that target_vcn falls within +[dp->vcn, dp->vcn + dp->lcns_follow), i.e., that the FIRST +cluster is covered. The walk through the further entries is +not bounded against dp->lcns_follow. For a malformed LRH +where target_vcn = dp->vcn + dp->lcns_follow - 1 and +lrh->lcns_follow > 1, the i > 0 writes overflow the dp's +allocated page_lcns[] array. + +Add the missing j + lrh->lcns_follow <= dp->lcns_follow guard. + +Reproduced under UML+KASAN on mainline 8d90b09e6741 as a +slab-out-of-bounds write of size 8 from log_replay+0x68d4 on +the mount path. + +This is distinct from Pavitra Jha's 2026-05-02 patch +("fs/ntfs3: validate lcns_follow in log_replay conversion", +<20260502154252.164586-1-jhapavitra98@gmail.com>) which +addresses the separate version-0 dirty-page-table conversion +path's memmove(&dp->vcn, ...) call. The two fixes are +complementary; both should land. + +Fixes: b46acd6a6a62 ("fs/ntfs3: Add NTFS journal") +Cc: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-7 +Signed-off-by: Michael Bommarito +[almaz.alexandrovich@paragon-software.com: clang-formatted the changes, +fixed conflicts] +Signed-off-by: Konstantin Komarov +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs3/fslog.c | 46 +++++++++++++++++++++++++++++----------------- + 1 file changed, 29 insertions(+), 17 deletions(-) + +--- a/fs/ntfs3/fslog.c ++++ b/fs/ntfs3/fslog.c +@@ -3369,8 +3369,8 @@ move_data: + + if (run_get_highest_vcn(le64_to_cpu(attr->nres.svcn), + attr_run(attr), +- le32_to_cpu(attr->size) - +- le16_to_cpu(attr->nres.run_off), ++ le32_to_cpu(attr->size) - ++ le16_to_cpu(attr->nres.run_off), + &t64)) { + goto dirty_vol; + } +@@ -4579,22 +4579,34 @@ copy_lcns: + * whole routine a loop, case Lcns do not fit below. + */ + t16 = le16_to_cpu(lrh->lcns_follow); +- t32 = le32_to_cpu(dp->lcns_follow); +- if (le64_to_cpu(lrh->target_vcn) < le64_to_cpu(dp->vcn)) { +- err = -EINVAL; +- goto out; +- } +- +- for (i = 0; i < t16; i++) { +- size_t j = (size_t)(le64_to_cpu(lrh->target_vcn) - +- le64_to_cpu(dp->vcn)); +- if (j >= t32 || i >= t32 - j) { +- err = -EINVAL; +- goto out; +- } +- dp->page_lcns[j + i] = lrh->page_lcns[i]; +- } ++ t32 = le32_to_cpu(dp->lcns_follow); ++ if (le64_to_cpu(lrh->target_vcn) < le64_to_cpu(dp->vcn)) { ++ err = -EINVAL; ++ goto out; ++ } ++ ++ /* ++ * find_dp() only validates that target_vcn is the first ++ * cluster covered by dp. The walk through lrh->lcns_follow ++ * further entries must stay within the allocated ++ * dp->page_lcns[] array, which is sized by dp->lcns_follow. ++ */ ++ if (le64_to_cpu(lrh->target_vcn) - le64_to_cpu(dp->vcn) + t16 > ++ le32_to_cpu(dp->lcns_follow)) { ++ err = -EINVAL; ++ log->set_dirty = true; ++ goto out; ++ } + ++ for (i = 0; i < t16; i++) { ++ size_t j = (size_t)(le64_to_cpu(lrh->target_vcn) - ++ le64_to_cpu(dp->vcn)); ++ if (j >= t32 || i >= t32 - j) { ++ err = -EINVAL; ++ goto out; ++ } ++ dp->page_lcns[j + i] = lrh->page_lcns[i]; ++ } + goto next_log_record_analyze; + + case DeleteDirtyClusters: { diff --git a/queue-7.1/fs-ntfs3-bound-deleteindexentryallocation-memmove-length.patch b/queue-7.1/fs-ntfs3-bound-deleteindexentryallocation-memmove-length.patch new file mode 100644 index 0000000000..29c5f03e26 --- /dev/null +++ b/queue-7.1/fs-ntfs3-bound-deleteindexentryallocation-memmove-length.patch @@ -0,0 +1,74 @@ +From fc4626bb3656362de8b0ecd56605d47a19ec3518 Mon Sep 17 00:00:00 2001 +From: Konstantin Komarov +Date: Tue, 2 Jun 2026 15:21:03 +0200 +Subject: fs/ntfs3: bound DeleteIndexEntryAllocation memmove length + +From: Konstantin Komarov + +commit fc4626bb3656362de8b0ecd56605d47a19ec3518 upstream. + +In do_action()'s DeleteIndexEntryAllocation case, e->size comes +from an on-disk INDEX_BUFFER entry. When e->size makes +e + e->size point past hdr + hdr->used, +PtrOffset(e1, Add2Ptr(hdr, used)) returns a negative ptrdiff_t +that is silently cast to a quasi-infinite size_t when passed +to memmove(). The memmove then walks past the destination +buffer. + +The sibling DeleteIndexEntryRoot case at fslog.c:3540-3543 +already carries the corresponding guard: + + if (PtrOffset(e1, Add2Ptr(hdr, used)) < esize || + Add2Ptr(e, esize) > Add2Ptr(lrh, rec_len) || + used + esize > le32_to_cpu(hdr->total)) { + goto dirty_vol; + } + +Apply the same shape to the allocation-path case. Also reject +esize == 0: memmove(e, e, ...) is a no-op and leaves +hdr->used unchanged, hiding a malformed entry from the +existing check_index_header() walk. + +Reproduced under UML+KASAN on mainline 8d90b09e6741 by +mounting a crafted NTFS image: the unguarded memmove takes a +length of 0xffffffffffffff00 and the kernel oopses in +memmove+0x81/0x1a0 on the do_action+0x36a2 frame. + +Fixes: b46acd6a6a62 ("fs/ntfs3: Add NTFS journal") +Cc: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-7 +Signed-off-by: Michael Bommarito +[almaz.alexandrovich@paragon-software.com: clang-formatted the changes] +Signed-off-by: Konstantin Komarov +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs3/fslog.c | 16 +++++++++++++++- + 1 file changed, 15 insertions(+), 1 deletion(-) + +--- a/fs/ntfs3/fslog.c ++++ b/fs/ntfs3/fslog.c +@@ -3573,9 +3573,23 @@ move_data: + } + + e1 = Add2Ptr(e, esize); +- nsize = esize; + used = le32_to_cpu(hdr->used); + ++ /* ++ * Reject crafted entries whose e->size makes e + esize ++ * point past the INDEX_HDR's used boundary. Without this, ++ * PtrOffset(e1, hdr + used) underflows to a quasi-infinite ++ * size_t when fed to the memmove() below. ++ * ++ * Also reject esize == 0: memmove(e, e, ...) is a no-op and ++ * leaves hdr->used unchanged, masking the crafted entry. ++ */ ++ if (!esize || Add2Ptr(e, esize) > Add2Ptr(hdr, used) || ++ PtrOffset(e1, Add2Ptr(hdr, used)) < esize) ++ goto dirty_vol; ++ ++ nsize = esize; ++ + memmove(e, e1, PtrOffset(e1, Add2Ptr(hdr, used))); + + hdr->used = cpu_to_le32(used - nsize); diff --git a/queue-7.1/fs-ntfs3-bound-ntfs_de-view.data_off-in-updaterecorddata-root-allocation.patch b/queue-7.1/fs-ntfs3-bound-ntfs_de-view.data_off-in-updaterecorddata-root-allocation.patch new file mode 100644 index 0000000000..c067f62e2a --- /dev/null +++ b/queue-7.1/fs-ntfs3-bound-ntfs_de-view.data_off-in-updaterecorddata-root-allocation.patch @@ -0,0 +1,80 @@ +From 3e127829e57f5190f612412ece4541cb96d5ec7a Mon Sep 17 00:00:00 2001 +From: Michael Bommarito +Date: Tue, 19 May 2026 05:51:35 -0400 +Subject: fs/ntfs3: bound NTFS_DE view.data_off in UpdateRecordData{Root,Allocation} + +From: Michael Bommarito + +commit 3e127829e57f5190f612412ece4541cb96d5ec7a upstream. + +In do_action()'s UpdateRecordDataRoot (fslog.c:3489) and +UpdateRecordDataAllocation (fslog.c:3697) cases, the memmove +destination is `Add2Ptr(e, le16_to_cpu(e->view.data_off))`, +where e->view.data_off comes from an on-disk NTFS_DE inside +an INDEX_ROOT or INDEX_BUFFER. Neither case validates +view.data_off + dlen against e->size; the existing +check_if_index_root / check_if_alloc_index helpers walk the +entry chain and validate the entry's offset, but not its +internal view fields. + +The neighbouring read sites (e.g., fs/ntfs3/index.c when +iterating view entries) check view.data_off + view.data_size +<= e->size. Apply the same bound at the two memmove sites. + +Reproduced under UML+KASAN on mainline 8d90b09e6741 via +pr_warn-only probe instrumentation: with view.data_off forced +to 0xFFFC, the memmove writes 32 bytes past the end of the +NTFS_DE. + +This is similar in shape to Pavitra Jha's 2026-05-02 patch +"fs/ntfs3: prevent oob in case UpdateRecordDataRoot" +(<20260502105008.21827-1-jhapavitra98@gmail.com>) which +proposes calling ntfs3_bad_de_range(); that helper does not +exist in mainline. This patch uses inline checks. + +Fixes: b46acd6a6a62 ("fs/ntfs3: Add NTFS journal") +Cc: stable@vger.kernel.org +Reported-by: Pavitra Jha +Closes: https://lore.kernel.org/ntfs3/20260502105008.21827-1-jhapavitra98@gmail.com/ +Assisted-by: Claude:claude-opus-4-7 +Signed-off-by: Michael Bommarito +Signed-off-by: Konstantin Komarov +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs3/fslog.c | 18 ++++++++++++++++++ + 1 file changed, 18 insertions(+) + +--- a/fs/ntfs3/fslog.c ++++ b/fs/ntfs3/fslog.c +@@ -3511,6 +3511,18 @@ move_data: + + e = Add2Ptr(attr, le16_to_cpu(lrh->attr_off)); + ++ /* ++ * e->view.data_off and dlen come from the on-disk ++ * INDEX_ROOT entry / LRH. The neighbouring read sites ++ * (e.g. fs/ntfs3/index.c) check that ++ * view.data_off + view.data_size <= e->size; mirror that ++ * bound here so the memmove cannot reach past the entry. ++ */ ++ if (le16_to_cpu(e->view.data_off) > le16_to_cpu(e->size) || ++ le16_to_cpu(e->view.data_off) + dlen > ++ le16_to_cpu(e->size)) ++ goto dirty_vol; ++ + memmove(Add2Ptr(e, le16_to_cpu(e->view.data_off)), data, dlen); + + mi->dirty = true; +@@ -3717,6 +3729,12 @@ move_data: + goto dirty_vol; + } + ++ /* See UpdateRecordDataRoot for the rationale. */ ++ if (le16_to_cpu(e->view.data_off) > le16_to_cpu(e->size) || ++ le16_to_cpu(e->view.data_off) + dlen > ++ le16_to_cpu(e->size)) ++ goto dirty_vol; ++ + memmove(Add2Ptr(e, le16_to_cpu(e->view.data_off)), data, dlen); + + a_dirty = true; diff --git a/queue-7.1/fs-ntfs3-fix-syncing-wrong-inode-on-dirsync-cross-directory-rename.patch b/queue-7.1/fs-ntfs3-fix-syncing-wrong-inode-on-dirsync-cross-directory-rename.patch new file mode 100644 index 0000000000..c5306a586f --- /dev/null +++ b/queue-7.1/fs-ntfs3-fix-syncing-wrong-inode-on-dirsync-cross-directory-rename.patch @@ -0,0 +1,45 @@ +From 932fa0c1496286e39e14e27194c1ee7c2181629f Mon Sep 17 00:00:00 2001 +From: Zhan Xusheng +Date: Wed, 6 May 2026 15:55:54 +0800 +Subject: fs/ntfs3: fix syncing wrong inode on DIRSYNC cross-directory rename + +From: Zhan Xusheng + +commit 932fa0c1496286e39e14e27194c1ee7c2181629f upstream. + +In ntfs3_rename(), when IS_DIRSYNC(new_dir) is true, the code syncs +the renamed file inode instead of the target directory new_dir: + if (IS_DIRSYNC(new_dir)) + ntfs_sync_inode(inode); /* should be new_dir */ + +DIRSYNC requires that directory metadata changes are written to disk +synchronously. Since new_dir was modified (a new directory entry was +added), it is new_dir that must be synced to satisfy the guarantee, +not the renamed file itself. + +This bug has existed since the initial ntfs3 implementation and was +carried through the refactoring in commit 78ab59fee07f +("fs/ntfs3: Rework file operations"). + +Fix by syncing new_dir instead of inode. + +Fixes: 4342306f0f0d ("fs/ntfs3: Add file operations and implementation") +Cc: stable@vger.kernel.org +Signed-off-by: Zhan Xusheng +Signed-off-by: Konstantin Komarov +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs3/namei.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/fs/ntfs3/namei.c ++++ b/fs/ntfs3/namei.c +@@ -340,7 +340,7 @@ static int ntfs_rename(struct mnt_idmap + ntfs_sync_inode(dir); + + if (IS_DIRSYNC(new_dir)) +- ntfs_sync_inode(inode); ++ ntfs_sync_inode(new_dir); + } + + if (dir_ni != new_dir_ni) diff --git a/queue-7.1/fs-ntfs3-validate-lcns_follow-in-log_replay-conversion.patch b/queue-7.1/fs-ntfs3-validate-lcns_follow-in-log_replay-conversion.patch new file mode 100644 index 0000000000..601117e57c --- /dev/null +++ b/queue-7.1/fs-ntfs3-validate-lcns_follow-in-log_replay-conversion.patch @@ -0,0 +1,76 @@ +From 6a4c53a2e26a865565bd6a460961e8d6fcb32329 Mon Sep 17 00:00:00 2001 +From: Konstantin Komarov +Date: Mon, 1 Jun 2026 10:57:56 +0200 +Subject: fs/ntfs3: validate lcns_follow in log_replay conversion + +From: Konstantin Komarov + +commit 6a4c53a2e26a865565bd6a460961e8d6fcb32329 upstream. + +log_replay() converts DIR_PAGE_ENTRY_32 records into DIR_PAGE_ENTRY +records when replaying version 0 restart tables. + +During this conversion, the memmove() length is derived directly from +the on-disk lcns_follow field: + + memmove(&dp->vcn, &dp0->vcn_low, + 2 * sizeof(u64) + + le32_to_cpu(dp->lcns_follow) * sizeof(u64)); + +check_rstbl() validates restart table structure, but does not constrain +per-entry lcns_follow values relative to the entry size. A malformed +filesystem image can provide an oversized lcns_follow value, causing +the conversion memmove() to access memory beyond the bounds of the +allocated restart table buffer. + +The same field is later used to bound iteration over page_lcns[], +so validating lcns_follow during conversion also prevents downstream +out-of-bounds access from the same malformed metadata. + +Compute the maximum valid lcns_follow from the already-validated +restart table entry size and reject entries that exceed this bound. +Reuse the existing t16/t32 scratch variables already declared in +log_replay() to avoid introducing new declarations. + +Fixes: b46acd6a6a62 ("fs/ntfs3: Add NTFS journal") +Cc: stable@vger.kernel.org +Signed-off-by: Pavitra Jha +[almaz.alexandrovich@paragon-software.com: fixed the conflicts] +Signed-off-by: Konstantin Komarov +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs3/fslog.c | 19 ++++++++++++++++--- + 1 file changed, 16 insertions(+), 3 deletions(-) + +--- a/fs/ntfs3/fslog.c ++++ b/fs/ntfs3/fslog.c +@@ -4262,13 +4262,26 @@ check_dirty_page_table: + if (rst->major_ver) + goto end_conv_1; /* reduce tab pressure. */ + ++ t16 = le16_to_cpu(dptbl->size); ++ if (t16 < sizeof(struct DIR_PAGE_ENTRY)) { ++ log->set_dirty = true; ++ goto out; ++ } ++ ++ t32 = (t16 - sizeof(struct DIR_PAGE_ENTRY)) / sizeof(u64); ++ + dp = NULL; + while ((dp = enum_rstbl(dptbl, dp))) { + struct DIR_PAGE_ENTRY_32 *dp0 = (struct DIR_PAGE_ENTRY_32 *)dp; +- // NOTE: Danger. Check for of boundary. ++ u32 lcns = le32_to_cpu(dp->lcns_follow); ++ ++ if (lcns > t32) { ++ log->set_dirty = true; ++ goto out; ++ } ++ + memmove(&dp->vcn, &dp0->vcn_low, +- 2 * sizeof(u64) + +- le32_to_cpu(dp->lcns_follow) * sizeof(u64)); ++ 2 * sizeof(u64) + lcns * sizeof(u64)); + } + + end_conv_1: diff --git a/queue-7.1/fs-proc-fix-kpf_ksm-reported-for-all-anonymous-pages.patch b/queue-7.1/fs-proc-fix-kpf_ksm-reported-for-all-anonymous-pages.patch new file mode 100644 index 0000000000..1e0dbaa726 --- /dev/null +++ b/queue-7.1/fs-proc-fix-kpf_ksm-reported-for-all-anonymous-pages.patch @@ -0,0 +1,52 @@ +From 81401cebfc1598306b0a981b5f9ee5b58c1aac52 Mon Sep 17 00:00:00 2001 +From: Jinjiang Tu +Date: Fri, 26 Jun 2026 09:32:52 +0800 +Subject: fs/proc: fix KPF_KSM reported for all anonymous pages + +From: Jinjiang Tu + +commit 81401cebfc1598306b0a981b5f9ee5b58c1aac52 upstream. + +Reading /proc/kpageflags for any anonymous page returns KPF_KSM set, even +when KSM is not in use. As a result, tools misclassify all anonymous +pages as KSM merged. + +In stable_page_flags(), if the page is anonymous, then use (mapping & +FOLIO_MAPPING_KSM) check to identify if the anonymous page is KSM page. +However, FOLIO_MAPPING_KSM is FOLIO_MAPPING_ANON | FOLIO_MAPPING_ANON_KSM, +(mapping & FOLIO_MAPPING_KSM) check returns true for all anonymous pages. + +To fix it, use FOLIO_MAPPING_ANON_KSM instead. + +Link: https://lore.kernel.org/20260629033122.774318-1-tujinjiang@huawei.com +Link: https://lore.kernel.org/20260626013252.2846774-1-tujinjiang@huawei.com +Fixes: dee3d0bef2b0 ("proc: rewrite stable_page_flags()") +Signed-off-by: Jinjiang Tu +Acked-by: David Hildenbrand (Arm) +Acked-by: Zi Yan +Reviewed-by: Xu Xin +Cc: Chengming Zhou +Cc: Kefeng Wang +Cc: Luiz Capitulino +Cc: Matthew Wilcox (Oracle) +Cc: Miaohe Lin +Cc: Nanyong Sun +Cc: Svetly Todorov +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + fs/proc/page.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/fs/proc/page.c ++++ b/fs/proc/page.c +@@ -173,7 +173,7 @@ u64 stable_page_flags(const struct page + u |= 1 << KPF_MMAP; + if (is_anon) { + u |= 1 << KPF_ANON; +- if (mapping & FOLIO_MAPPING_KSM) ++ if ((mapping & FOLIO_MAPPING_FLAGS) == FOLIO_MAPPING_KSM) + u |= 1 << KPF_KSM; + } + diff --git a/queue-7.1/fs-proc-task_mmu-do-not-warn-on-seeing-non-migration-pmd-entry.patch b/queue-7.1/fs-proc-task_mmu-do-not-warn-on-seeing-non-migration-pmd-entry.patch new file mode 100644 index 0000000000..fe65ff6d8e --- /dev/null +++ b/queue-7.1/fs-proc-task_mmu-do-not-warn-on-seeing-non-migration-pmd-entry.patch @@ -0,0 +1,52 @@ +From cd1fc0e3c1f67c0c31dfc215e5d9b771133dedc0 Mon Sep 17 00:00:00 2001 +From: Dev Jain +Date: Thu, 4 Jun 2026 05:53:05 +0000 +Subject: fs/proc/task_mmu: do not warn on seeing non-migration pmd entry + +From: Dev Jain + +commit cd1fc0e3c1f67c0c31dfc215e5d9b771133dedc0 upstream. + +Patch series "mm/hmm: A fix and a selftest", v3. + +Patch 1 fixes a stale warning present from the time when only migration +softleaf entries were supported at the PMD level. + +Patch 2 adds some code into hmm-tests.c which exercises the pagemap path +for PMD device-private entries. + + +This patch (of 2): + +pagemap_pmd_range_thp() warns if a non-present PMD is not a migration +entry. This became false once device-private entries at the PMD level +were added. + +Therefore, remove the stale migration-only assertion. + +Link: https://lore.kernel.org/20260604055308.1947679-1-dev.jain@arm.com +Link: https://lore.kernel.org/20260604055308.1947679-2-dev.jain@arm.com +Fixes: a30b48bf1b24 ("mm/migrate_device: implement THP migration of zone device pages") +Signed-off-by: Dev Jain +Reviewed-by: Balbir Singh +Reviewed-by: Lorenzo Stoakes +Tested-by: Lorenzo Stoakes +Acked-by: David Hildenbrand (Arm) +Reviewed-by: Oscar Salvador (SUSE) +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + fs/proc/task_mmu.c | 1 - + 1 file changed, 1 deletion(-) + +--- a/fs/proc/task_mmu.c ++++ b/fs/proc/task_mmu.c +@@ -2042,7 +2042,6 @@ static int pagemap_pmd_range_thp(pmd_t * + flags |= PM_SOFT_DIRTY; + if (pmd_swp_uffd_wp(pmd)) + flags |= PM_UFFD_WP; +- VM_WARN_ON_ONCE(!pmd_is_migration_entry(pmd)); + page = softleaf_to_page(entry); + } + diff --git a/queue-7.1/fs-proc-task_mmu-fix-hugetlb-self-deadlock-in-pagemap_scan_pte_hole.patch b/queue-7.1/fs-proc-task_mmu-fix-hugetlb-self-deadlock-in-pagemap_scan_pte_hole.patch new file mode 100644 index 0000000000..fce4d011cb --- /dev/null +++ b/queue-7.1/fs-proc-task_mmu-fix-hugetlb-self-deadlock-in-pagemap_scan_pte_hole.patch @@ -0,0 +1,140 @@ +From e92d92bbafb264dc0518d52b846a3c07ed8d523f Mon Sep 17 00:00:00 2001 +From: "Kiryl Shutsemau (Meta)" +Date: Fri, 29 May 2026 18:23:27 +0100 +Subject: fs/proc/task_mmu: fix hugetlb self-deadlock in pagemap_scan_pte_hole() + +From: Kiryl Shutsemau (Meta) + +commit e92d92bbafb264dc0518d52b846a3c07ed8d523f upstream. + +A PAGEMAP_SCAN ioctl requesting PM_SCAN_WP_MATCHING on a hugetlb VMA hangs +the calling thread, unkillably, as soon as the scan reaches an unpopulated +part of the range: + + do_pagemap_scan() + walk_page_range() + walk_hugetlb_range() + hugetlb_vma_lock_read() # take the vma lock for read ... + pagemap_scan_pte_hole() # ... ->pte_hole() for a hole + uffd_wp_range() + change_protection() + hugetlb_change_protection() + hugetlb_vma_lock_write() # ... and block taking it for write + +walk_hugetlb_range() holds the hugetlb vma lock for read across the whole +walk. A present entry goes to ->hugetlb_entry(); an unpopulated one goes +to ->pte_hole(), i.e. pagemap_scan_pte_hole(). To write-protect the hole +that handler calls uffd_wp_range(), which on a hugetlb VMA reaches +hugetlb_change_protection() and takes the same vma lock for write. The +thread then blocks in down_write() waiting for the read lock it is itself +holding. + +The populated path avoids this: pagemap_scan_hugetlb_entry() +write-protects the entry inline under the page-table lock and never enters +hugetlb_change_protection(). + +Do the same for holes. Fault in the page table and install the uffd-wp +marker directly with make_uffd_wp_huge_pte() under the page-table lock, +rather than routing through uffd_wp_range(). That is the same sequence +hugetlb_change_protection() runs for an unpopulated entry, minus the vma +write lock -- which is safe to skip because PMD sharing is disabled on +uffd-wp VMAs (hugetlb_unshare_all_pmds() runs at registration), leaving +nothing for that lock to serialise against. + +Link: https://lore.kernel.org/20260529172331.356655-4-kas@kernel.org +Fixes: 52526ca7fdb9 ("fs/proc/task_mmu: implement IOCTL to get and optionally clear info about PTEs") +Signed-off-by: Kiryl Shutsemau +Reported-by: Sashiko AI review +Assisted-by: Claude:claude-opus-4-8 +Cc: David Hildenbrand +Cc: Lorenzo Stoakes +Cc: Michal Hocko +Cc: Mike Rapoport +Cc: Peter Xu +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Cc: Balbir Singh +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + fs/proc/task_mmu.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 58 insertions(+), 1 deletion(-) + +--- a/fs/proc/task_mmu.c ++++ b/fs/proc/task_mmu.c +@@ -2890,8 +2890,62 @@ out_unlock: + + return ret; + } ++ ++/* ++ * Write-protect the unpopulated hugetlb entries covering [addr, end) by ++ * installing uffd-wp markers inline, exactly as pagemap_scan_hugetlb_entry() ++ * does for populated entries. ++ * ++ * walk_hugetlb_range() currently calls ->pte_hole() once per huge page, so the ++ * loop normally runs a single iteration; it is written to cover the full range ++ * in case the walker ever coalesces adjacent holes. ++ * ++ * The obvious route -- uffd_wp_range() -> hugetlb_change_protection() -- ++ * cannot be used here: it takes hugetlb_vma_lock_write(), but the page-table ++ * walker (walk_hugetlb_range()) already holds hugetlb_vma_lock_read() on the ++ * same VMA, so the scanning thread would deadlock against itself. PMD sharing ++ * is disabled on uffd-wp VMAs (hugetlb_unshare_all_pmds() at registration), so ++ * the vma lock guards nothing that matters for these entries anyway. ++ */ ++static int pagemap_scan_hugetlb_hole_wp(struct vm_area_struct *vma, ++ unsigned long addr, unsigned long end) ++{ ++ struct hstate *h = hstate_vma(vma); ++ unsigned long psize = huge_page_size(h); ++ struct mm_struct *mm = vma->vm_mm; ++ spinlock_t *ptl; ++ pte_t *ptep; ++ pte_t pte; ++ ++ for (addr = ALIGN_DOWN(addr, psize); addr < end; addr += psize) { ++ ptep = huge_pte_alloc(mm, vma, addr, psize); ++ if (!ptep) ++ return -ENOMEM; ++ ++ i_mmap_lock_write(vma->vm_file->f_mapping); ++ ptl = huge_pte_lock(h, mm, ptep); ++ pte = huge_ptep_get(mm, addr, ptep); ++ make_uffd_wp_huge_pte(vma, addr, ptep, pte); ++ /* ++ * A none entry has no cached translation, so installing the ++ * marker needs no TLB flush. Flush only if a fault populated ++ * the entry between huge_pte_alloc() and the page table lock. ++ */ ++ if (!huge_pte_none(pte)) ++ flush_hugetlb_tlb_range(vma, addr, addr + psize); ++ spin_unlock(ptl); ++ i_mmap_unlock_write(vma->vm_file->f_mapping); ++ } ++ ++ return 0; ++} + #else + #define pagemap_scan_hugetlb_entry NULL ++static int pagemap_scan_hugetlb_hole_wp(struct vm_area_struct *vma, ++ unsigned long addr, unsigned long end) ++{ ++ return 0; ++} + #endif + + static int pagemap_scan_pte_hole(unsigned long addr, unsigned long end, +@@ -2911,7 +2965,10 @@ static int pagemap_scan_pte_hole(unsigne + if (~p->arg.flags & PM_SCAN_WP_MATCHING) + return ret; + +- err = uffd_wp_range(vma, addr, end - addr, true); ++ if (is_vm_hugetlb_page(vma)) ++ err = pagemap_scan_hugetlb_hole_wp(vma, addr, end); ++ else ++ err = uffd_wp_range(vma, addr, end - addr, true); + if (err < 0) + ret = err; + diff --git a/queue-7.1/fs-proc-task_mmu-fix-make_uffd_wp_huge_pte-prot-update-race.patch b/queue-7.1/fs-proc-task_mmu-fix-make_uffd_wp_huge_pte-prot-update-race.patch new file mode 100644 index 0000000000..a9f77f6522 --- /dev/null +++ b/queue-7.1/fs-proc-task_mmu-fix-make_uffd_wp_huge_pte-prot-update-race.patch @@ -0,0 +1,112 @@ +From 04718f7c9290f95385f0dd328758753dc1c36dec Mon Sep 17 00:00:00 2001 +From: "Kiryl Shutsemau (Meta)" +Date: Fri, 29 May 2026 18:23:25 +0100 +Subject: fs/proc/task_mmu: fix make_uffd_wp_huge_pte() prot-update race + +From: Kiryl Shutsemau (Meta) + +commit 04718f7c9290f95385f0dd328758753dc1c36dec upstream. + +Patch series "userfaultfd/pagemap: pre-existing fixes". + +These are pre-existing bug fixes that were carried at the front of the +userfaultfd RWP working-set-tracking series up to v5 [1]. Per review +feedback that fixes should not sit in the middle of a feature series, they +are split out and sent on their own; the RWP series is reposted rebased on +top of this. + +All six were flagged by the Sashiko AI review of the RWP series and carry +Reported-by: Sashiko AI review . They are +independent of RWP, apply to mm-new directly, and carry Cc: stable@. + + 1: fs/proc/task_mmu: a missing huge_ptep_modify_prot_start() in + make_uffd_wp_huge_pte() can lose hardware Dirty/Accessed updates + when PAGEMAP_SCAN write-protects a hugetlb PTE. + + 2: fs/proc/task_mmu: pagemap_scan_hugetlb_entry() compares the range + against HPAGE_SIZE rather than the hstate page size, so it never + write-protects gigantic hugetlb pages. + + 3: fs/proc/task_mmu: PAGEMAP_SCAN with PM_SCAN_WP_MATCHING over an + unpopulated hugetlb range self-deadlocks -- pagemap_scan_pte_hole() + calls uffd_wp_range() while walk_hugetlb_range() holds the hugetlb + vma lock for read, and hugetlb_change_protection() then takes it + for write. Install the marker inline instead. + + 4: mm/huge_memory: change_non_present_huge_pmd() drops pmd_swp_uffd_wp + on a device-private PMD permission downgrade, silently losing the + uffd-wp marker. + + 5: userfaultfd: must_wait() applies pte_write() to a locklessly read + PTE without checking pte_present(), so swap/migration entries + decode random offset bits and a thread can stay parked on a stale + fault. + + 6: userfaultfd: __VMA_UFFD_FLAGS feeds VMA_UFFD_MINOR_BIT (41) to + mk_vma_flags() unconditionally, an out-of-bounds write into the + single-word vma_flags_t on 32-bit. Build the mask from config-gated + per-mode masks so an unavailable bit is never materialised. + + +This patch (of 6): + +make_uffd_wp_huge_pte() arms the UFFD_WP bit on a present HugeTLB PTE by +calling huge_ptep_modify_prot_commit() with a ptent snapshot that was +fetched without the corresponding huge_ptep_modify_prot_start(). The +start helper is what atomically clears the entry so the kernel-owned +snapshot stays consistent until the commit; without it, the hardware may +set Dirty or Accessed in the live PTE between the original read and the +commit, and huge_ptep_modify_prot_commit() (whose generic implementation +just calls set_huge_pte_at()) then writes the stale snapshot back over the +live hardware bits, losing the update. + +The non-hugetlb sibling make_uffd_wp_pte() does this correctly via +ptep_modify_prot_start() / ptep_modify_prot_commit(). Mirror that pattern +for the present-PTE branch. The migration case stays as-is -- migration +entries are non-present, so there's no hardware update to race against. + +Link: https://lore.kernel.org/20260529172331.356655-1-kas@kernel.org +Link: https://lore.kernel.org/20260529172331.356655-2-kas@kernel.org +Link: https://lore.kernel.org/all/20260526130509.2748441-1-kirill@shutemov.name/ [1] +Fixes: 52526ca7fdb9 ("fs/proc/task_mmu: implement IOCTL to get and optionally clear info about PTEs") +Signed-off-by: Kiryl Shutsemau +Reported-by: Sashiko AI review +Reviewed-by: Lorenzo Stoakes +Reviewed-by: Dev Jain +Cc: David Hildenbrand +Cc: Michal Hocko +Cc: Mike Rapoport +Cc: Peter Xu +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Cc: Balbir Singh +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + fs/proc/task_mmu.c | 12 ++++++++---- + 1 file changed, 8 insertions(+), 4 deletions(-) + +--- a/fs/proc/task_mmu.c ++++ b/fs/proc/task_mmu.c +@@ -2523,12 +2523,16 @@ static void make_uffd_wp_huge_pte(struct + if (softleaf_is_hwpoison(entry) || softleaf_is_marker(entry)) + return; + +- if (softleaf_is_migration(entry)) ++ if (softleaf_is_migration(entry)) { + set_huge_pte_at(vma->vm_mm, addr, ptep, + pte_swp_mkuffd_wp(ptent), psize); +- else +- huge_ptep_modify_prot_commit(vma, addr, ptep, ptent, +- huge_pte_mkuffd_wp(ptent)); ++ } else { ++ pte_t old_pte, new_pte; ++ ++ old_pte = huge_ptep_modify_prot_start(vma, addr, ptep); ++ new_pte = huge_pte_mkuffd_wp(old_pte); ++ huge_ptep_modify_prot_commit(vma, addr, ptep, old_pte, new_pte); ++ } + } + #endif /* CONFIG_HUGETLB_PAGE */ + diff --git a/queue-7.1/fs-proc-task_mmu-use-huge_page_size-in-pagemap_scan_hugetlb_entry.patch b/queue-7.1/fs-proc-task_mmu-use-huge_page_size-in-pagemap_scan_hugetlb_entry.patch new file mode 100644 index 0000000000..cf37d855b2 --- /dev/null +++ b/queue-7.1/fs-proc-task_mmu-use-huge_page_size-in-pagemap_scan_hugetlb_entry.patch @@ -0,0 +1,46 @@ +From 1b074e3270e1c061c829150c742eb83bad4dddd1 Mon Sep 17 00:00:00 2001 +From: "Kiryl Shutsemau (Meta)" +Date: Fri, 29 May 2026 18:23:26 +0100 +Subject: fs/proc/task_mmu: use huge_page_size() in pagemap_scan_hugetlb_entry() + +From: Kiryl Shutsemau (Meta) + +commit 1b074e3270e1c061c829150c742eb83bad4dddd1 upstream. + +The partial-page check compares against HPAGE_SIZE (PMD_SIZE), which is +wrong for gigantic hugetlb hstates (e.g. 1G). The walker hands the +callback a huge_page_size()-sized range, never start + HPAGE_SIZE, so the +comparison always declares it partial and aborts the WP. Compare against +the actual hstate's page size. + +Link: https://lore.kernel.org/20260529172331.356655-3-kas@kernel.org +Fixes: 52526ca7fdb9 ("fs/proc/task_mmu: implement IOCTL to get and optionally clear info about PTEs") +Signed-off-by: Kiryl Shutsemau +Reported-by: Sashiko AI review +Reviewed-by: Lorenzo Stoakes +Reviewed-by: Dev Jain +Cc: David Hildenbrand +Cc: Michal Hocko +Cc: Mike Rapoport +Cc: Peter Xu +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Cc: Balbir Singh +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + fs/proc/task_mmu.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/fs/proc/task_mmu.c ++++ b/fs/proc/task_mmu.c +@@ -2873,7 +2873,7 @@ static int pagemap_scan_hugetlb_entry(pt + if (~categories & PAGE_IS_WRITTEN) + goto out_unlock; + +- if (end != start + HPAGE_SIZE) { ++ if (end != start + huge_page_size(hstate_vma(vma))) { + /* Partial HugeTLB page WP isn't possible. */ + pagemap_scan_backout_range(p, start, end); + p->arg.walk_end = start; diff --git a/queue-7.1/kcov-use-write_once-for-selftest-mode-stores.patch b/queue-7.1/kcov-use-write_once-for-selftest-mode-stores.patch new file mode 100644 index 0000000000..796f184669 --- /dev/null +++ b/queue-7.1/kcov-use-write_once-for-selftest-mode-stores.patch @@ -0,0 +1,68 @@ +From 9a79524d1420e6b79a6868208c264f4518d1318e Mon Sep 17 00:00:00 2001 +From: Karl Mehltretter +Date: Tue, 26 May 2026 13:47:15 +0200 +Subject: kcov: use WRITE_ONCE() for selftest mode stores + +From: Karl Mehltretter + +commit 9a79524d1420e6b79a6868208c264f4518d1318e upstream. + +The KCOV selftest enables coverage by setting current->kcov_mode to +KCOV_MODE_TRACE_PC without installing a coverage area. If an interrupt +records coverage in that window, the access should fault and expose the +bug. + +When building for QEMU raspi0 (Raspberry Pi Zero, ARMv6, CONFIG_CPU_V6K=y, +CONFIG_CURRENT_POINTER_IN_TPIDRURO=y) with GCC 13.3.0, the store that +enables the mode is removed. The generated kcov_init() code only stores +zero after the wait loop: + + mrc 15, 0, r3, cr13, cr0, {3} + str r4, [r3, #2028] + +where r4 is zero. There is no store of KCOV_MODE_TRACE_PC before the +loop, so the selftest reports success without exercising coverage. + +Use WRITE_ONCE() for the temporary mode stores. With the same compiler +and config, kcov_init() contains the intended mode store: + + mov r3, #2 + mrc 15, 0, r2, cr13, cr0, {3} + str r3, [r2, #2028] + +Now that the KCOV selftest is actually executed, it may expose KCOV +instrumentation issues depending on the kernel config. That is expected +for a selftest that was intended to catch coverage from interrupt paths. + +Link: https://lore.kernel.org/20260526114715.38280-1-kmehltretter@gmail.com +Fixes: 6cd0dd934b03 ("kcov: Add interrupt handling self test") +Assisted-by: Codex:gpt-5 +Signed-off-by: Karl Mehltretter +Reviewed-by: Alexander Potapenko +Cc: Andrey Konovalov +Cc: Dmitry Vyukov +Cc: Kees Cook +Cc: Marco Elver +Cc: Peter Zijlstra +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + kernel/kcov.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/kernel/kcov.c ++++ b/kernel/kcov.c +@@ -1109,10 +1109,10 @@ static void __init selftest(void) + * potentially traced functions in this region. + */ + start = jiffies; +- current->kcov_mode = KCOV_MODE_TRACE_PC; ++ WRITE_ONCE(current->kcov_mode, KCOV_MODE_TRACE_PC); + while ((jiffies - start) * MSEC_PER_SEC / HZ < 300) + ; +- current->kcov_mode = 0; ++ WRITE_ONCE(current->kcov_mode, 0); + pr_err("done running self test\n"); + } + #endif diff --git a/queue-7.1/kho-make-sure-scratch-size-is-always-aligned-by-cma_min_alignment_bytes.patch b/queue-7.1/kho-make-sure-scratch-size-is-always-aligned-by-cma_min_alignment_bytes.patch new file mode 100644 index 0000000000..67d88b9814 --- /dev/null +++ b/queue-7.1/kho-make-sure-scratch-size-is-always-aligned-by-cma_min_alignment_bytes.patch @@ -0,0 +1,72 @@ +From 0e39380a7316122e1b00012b3f3cd3e318b3e7d3 Mon Sep 17 00:00:00 2001 +From: "Pratyush Yadav (Google)" +Date: Tue, 19 May 2026 18:05:49 +0200 +Subject: kho: make sure scratch size is always aligned by CMA_MIN_ALIGNMENT_BYTES + +From: Pratyush Yadav (Google) + +commit 0e39380a7316122e1b00012b3f3cd3e318b3e7d3 upstream. + +When using scratch_scale, the scratch sizes are rounded up to +CMA_MIN_ALIGNMENT_BYTES since they will be released as MIGRATE_CMA. This +is not done when using fixed scratch sizes via command line. This can +result in user specifying a size which is not aligned, and thus kernel +releasing a pageblock that is only partially scratch. + +Do the rounding up for both cases in scratch_size_update(). + +Fixes: 3dc92c311498 ("kexec: add Kexec HandOver (KHO) generation helpers") +Cc: stable@kernel.org +Signed-off-by: Pratyush Yadav (Google) +Link: https://patch.msgid.link/20260519160554.2713361-1-pratyush@kernel.org +Signed-off-by: Pasha Tatashin +Signed-off-by: Mike Rapoport (Microsoft) +Signed-off-by: Greg Kroah-Hartman +--- + kernel/liveupdate/kexec_handover.c | 32 +++++++++++++++++++++----------- + 1 file changed, 21 insertions(+), 11 deletions(-) + +--- a/kernel/liveupdate/kexec_handover.c ++++ b/kernel/liveupdate/kexec_handover.c +@@ -593,20 +593,30 @@ early_param("kho_scratch", kho_parse_scr + + static void __init scratch_size_update(void) + { +- phys_addr_t size; ++ /* ++ * If fixed sizes are not provided via command line, calculate them ++ * now. ++ */ ++ if (scratch_scale) { ++ phys_addr_t size; + +- if (!scratch_scale) +- return; ++ size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT, ++ NUMA_NO_NODE); ++ size = size * scratch_scale / 100; ++ scratch_size_lowmem = size; + +- size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT, +- NUMA_NO_NODE); +- size = size * scratch_scale / 100; +- scratch_size_lowmem = round_up(size, CMA_MIN_ALIGNMENT_BYTES); ++ size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE, ++ NUMA_NO_NODE); ++ size = size * scratch_scale / 100 - scratch_size_lowmem; ++ scratch_size_global = size; ++ } + +- size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE, +- NUMA_NO_NODE); +- size = size * scratch_scale / 100 - scratch_size_lowmem; +- scratch_size_global = round_up(size, CMA_MIN_ALIGNMENT_BYTES); ++ /* ++ * Scratch areas are released as MIGRATE_CMA. Round them up to the right ++ * size. ++ */ ++ scratch_size_lowmem = round_up(scratch_size_lowmem, CMA_MIN_ALIGNMENT_BYTES); ++ scratch_size_global = round_up(scratch_size_global, CMA_MIN_ALIGNMENT_BYTES); + } + + static phys_addr_t __init scratch_size_node(int nid) diff --git a/queue-7.1/landlock-fix-landlock_scope_signal-bypass-on-the-sigio-path.patch b/queue-7.1/landlock-fix-landlock_scope_signal-bypass-on-the-sigio-path.patch new file mode 100644 index 0000000000..51bc05ec4a --- /dev/null +++ b/queue-7.1/landlock-fix-landlock_scope_signal-bypass-on-the-sigio-path.patch @@ -0,0 +1,163 @@ +From 4b80320ca7ed03d6e683f95b6066565dc97b9f92 Mon Sep 17 00:00:00 2001 +From: Bryam Vargas +Date: Thu, 4 Jun 2026 23:16:56 +0000 +Subject: landlock: Fix LANDLOCK_SCOPE_SIGNAL bypass on the SIGIO path +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +From: Bryam Vargas + +commit 4b80320ca7ed03d6e683f95b6066565dc97b9f92 upstream. + +LANDLOCK_SCOPE_SIGNAL must prevent a sandboxed process from signaling +processes outside its Landlock domain. It can be bypassed through the +asynchronous SIGIO delivery path. + +A sandboxed process that owns any file or socket can arm it with +fcntl(fd, F_SETOWN, -pgid), fcntl(fd, F_SETSIG, SIGKILL) and O_ASYNC, so +that an I/O event makes the kernel deliver the chosen signal to the +whole process group. As the head of its process group's task list (the +default position right after fork()) that group can also hold the +non-sandboxed process that launched it, e.g. a supervisor or a security +monitor. The sandbox can thus kill or signal the processes +LANDLOCK_SCOPE_SIGNAL is meant to protect from it. + +The scope is enforced in hook_file_send_sigiotask() against the Landlock +domain recorded at F_SETOWN time, not the live domain of the sender. +control_current_fowner() decides whether to record that domain and skips +recording it when the fowner target is in the caller's thread group, +which is safe only for a single-task target (PIDTYPE_PID, PIDTYPE_TGID). +For a process group (PIDTYPE_PGID) pid_task() returns only one member; +recording is skipped whenever that member shares the caller's thread +group, and hook_file_send_sigiotask() then lets the signal fan out to +the whole group unchecked. + +Record the domain for every non single-process target so the scope is +enforced against each group member at delivery time. + +That recording is necessary but not sufficient on its own: the kernel +signals a process group through its members' thread-group leaders, and +the leader of the registrant's own process can carry a different +Landlock domain than the sibling thread that armed the owner. +domain_is_scoped() would then deny that leader, even though commit +18eb75f3af40 ("landlock: Always allow signals between threads of the +same process") requires same-process delivery to be allowed. +hook_task_kill() avoids this by evaluating same_thread_group() live, per +recipient; the SIGIO path instead delegates the whole decision to a +single registration-time check, which a process-group fan-out cannot +honor. + +So also record the registrant's thread group next to its domain and +exempt it at delivery: hook_file_send_sigiotask() allows the signal +whenever the recipient belongs to the registrant's own process, +restoring the same-process guarantee while keeping out-of-domain group +members blocked. The direct kill() path (hook_task_kill) already +evaluates the live domain and is unaffected. + +Fixes: 18eb75f3af40 ("landlock: Always allow signals between threads of the same process") +Cc: stable@vger.kernel.org +Signed-off-by: Bryam Vargas +Reviewed-by: Günther Noack +Link: https://patch.msgid.link/56bffc24f3d0d08b45a686a48e99766b0a0821fa.1780614610.git.hexlabsecurity@proton.me +[mic: Check pid_type earlier and improve comment, fix commit message, +fix comment formatting] +Signed-off-by: Mickaël Salaün +Signed-off-by: Greg Kroah-Hartman +--- + security/landlock/fs.c | 14 ++++++++++++++ + security/landlock/fs.h | 10 ++++++++++ + security/landlock/task.c | 11 +++++++++++ + 3 files changed, 35 insertions(+) + +--- a/security/landlock/fs.c ++++ b/security/landlock/fs.c +@@ -1901,6 +1901,14 @@ static bool control_current_fowner(struc + lockdep_assert_held(&fown->lock); + + /* ++ * A process-group or session owner (PIDTYPE_PGID/PIDTYPE_SID) fans the ++ * signal out to every member at delivery time, so record the domain and ++ * let hook_file_send_sigiotask() check the live scope per recipient. ++ */ ++ if (fown->pid_type != PIDTYPE_PID && fown->pid_type != PIDTYPE_TGID) ++ return true; ++ ++ /* + * Some callers (e.g. fcntl_dirnotify) may not be in an RCU read-side + * critical section. + */ +@@ -1916,6 +1924,7 @@ static void hook_file_set_fowner(struct + { + struct landlock_ruleset *prev_dom; + struct landlock_cred_security fown_subject = {}; ++ struct pid *prev_tg, *fown_tg = NULL; + size_t fown_layer = 0; + + if (control_current_fowner(file_f_owner(file))) { +@@ -1928,21 +1937,26 @@ static void hook_file_set_fowner(struct + if (new_subject) { + landlock_get_ruleset(new_subject->domain); + fown_subject = *new_subject; ++ fown_tg = get_pid(task_tgid(current)); + } + } + + prev_dom = landlock_file(file)->fown_subject.domain; ++ prev_tg = landlock_file(file)->fown_tg; + landlock_file(file)->fown_subject = fown_subject; ++ landlock_file(file)->fown_tg = fown_tg; + #ifdef CONFIG_AUDIT + landlock_file(file)->fown_layer = fown_layer; + #endif /* CONFIG_AUDIT*/ + + /* May be called in an RCU read-side critical section. */ + landlock_put_ruleset_deferred(prev_dom); ++ put_pid(prev_tg); + } + + static void hook_file_free_security(struct file *file) + { ++ put_pid(landlock_file(file)->fown_tg); + landlock_put_ruleset_deferred(landlock_file(file)->fown_subject.domain); + } + +--- a/security/landlock/fs.h ++++ b/security/landlock/fs.h +@@ -78,6 +78,16 @@ struct landlock_file_security { + * euid. + */ + struct landlock_cred_security fown_subject; ++ /** ++ * @fown_tg: Thread group of the task that set the file owner, pinned ++ * while @fown_subject holds a domain. It lets ++ * hook_file_send_sigiotask() always allow a SIGIO delivered to the ++ * owner's own process -- e.g. the thread-group leader reached through a ++ * process-group owner -- matching the same-process exemption of ++ * hook_task_kill(). NULL when no domain is recorded. Protected by ++ * file->f_owner->lock, like @fown_subject. ++ */ ++ struct pid *fown_tg; + }; + + #ifdef CONFIG_AUDIT +--- a/security/landlock/task.c ++++ b/security/landlock/task.c +@@ -411,6 +411,17 @@ static int hook_file_send_sigiotask(stru + if (!subject->domain) + return 0; + ++ /* ++ * Always allow delivery to the file owner's own process, including a ++ * thread-group leader reached through a process-group owner. This ++ * mirrors hook_task_kill()'s same-process exemption and preserves the ++ * guarantee of commit 18eb75f3af40 ("landlock: Always allow signals ++ * between threads of the same process"), which the registration-time ++ * check cannot honor for a process-group target. ++ */ ++ if (task_tgid(tsk) == landlock_file(fown->file)->fown_tg) ++ return 0; ++ + scoped_guard(rcu) + { + is_scoped = domain_is_scoped(subject->domain, diff --git a/queue-7.1/mips-dec-ensure-32-bit-stack-location-for-o32-prom_printf.patch b/queue-7.1/mips-dec-ensure-32-bit-stack-location-for-o32-prom_printf.patch new file mode 100644 index 0000000000..8ae2dba563 --- /dev/null +++ b/queue-7.1/mips-dec-ensure-32-bit-stack-location-for-o32-prom_printf.patch @@ -0,0 +1,123 @@ +From 5ff79e8bdc75db51e30298a75939e2308e7658e0 Mon Sep 17 00:00:00 2001 +From: "Maciej W. Rozycki" +Date: Wed, 6 May 2026 23:42:23 +0100 +Subject: MIPS: DEC: Ensure 32-bit stack location for o32 prom_printf() + +From: Maciej W. Rozycki + +commit 5ff79e8bdc75db51e30298a75939e2308e7658e0 upstream. + +In 64-bit configurations calling any firmware entry points from a kernel +thread other than the initial one will result in a situation where the +stack has been placed in the XKPHYS 64-bit memory segment. + +Consequently the stack pointer is no longer a 32-bit value and when the +32-bit firmware code called uses 32-bit ALU operations to manipulate the +stack pointer, the calculated result is incorrect (in fact in the 64-bit +MIPS ISA almost all 32-bit ALU operations will produce an unpredictable +result when executed on 64-bit data) and control goes astray. + +This may happen when no final console driver has been enabled in the +configuration and consequently the initial console continues being used +late into bootstrap, or with an upcoming change that will switch the zs +driver to use a platform device, which in turn will make the console +handover happen only after other kernel threads have already been +started, and the kernel will hang at: + + pid_max: default: 32768 minimum: 301 + +or somewhat later, but always before: + + cblist_init_generic: Setting adjustable number of callback queues. + +has been printed. + +It seems that only the prom_printf() entry point is affected. Of all +the other entry points wired only rex_slot_address() and rex_gettcinfo() +are called from a kernel thread other than the initial one, specifically +kernel_init(), and they are leaf functions that do no business with the +stack, having worked with no issue ever since 64-bit support was added +for the platform back in 2002. + +To address this issue then, arrange for the stack to be switched in the +o32 wrapper as required for prom_printf() only, by supplying call_o32() +with a pointer to a chunk of initdata space, which is placed in the +CKSEG0 32-bit compatibility segment, observing that prom_printf() is +only called from console output handler and therefore with the console +lock held, implying no need for this code to be reentrant. + +Other firmware entry points may be called with interrupts enabled and no +lock held, and may therefore require that call_o32() be reentrant. They +trigger no issue at this point and "if it ain't broke, don't fix it," so +just leave them alone. + +Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") +Signed-off-by: Maciej W. Rozycki +Cc: stable@vger.kernel.org # v2.6.12+ +Signed-off-by: Thomas Bogendoerfer +Signed-off-by: Greg Kroah-Hartman +--- + arch/mips/dec/prom/init.c | 6 +++++- + arch/mips/include/asm/dec/prom.h | 15 +++++++++++++-- + 2 files changed, 18 insertions(+), 3 deletions(-) + +--- a/arch/mips/dec/prom/init.c ++++ b/arch/mips/dec/prom/init.c +@@ -3,7 +3,7 @@ + * init.c: PROM library initialisation code. + * + * Copyright (C) 1998 Harald Koerfgen +- * Copyright (C) 2002, 2004 Maciej W. Rozycki ++ * Copyright (C) 2002, 2004, 2026 Maciej W. Rozycki + */ + #include + #include +@@ -20,6 +20,10 @@ + #include + + ++#ifdef CONFIG_64BIT ++unsigned long o32_stk[O32_STK_SIZE] __initdata = { 0 }; ++#endif ++ + int (*__rex_bootinit)(void); + int (*__rex_bootread)(void); + int (*__rex_getbitmap)(memmap *); +--- a/arch/mips/include/asm/dec/prom.h ++++ b/arch/mips/include/asm/dec/prom.h +@@ -4,7 +4,7 @@ + * + * DECstation PROM interface. + * +- * Copyright (C) 2002 Maciej W. Rozycki ++ * Copyright (C) 2002, 2026 Maciej W. Rozycki + * + * Based on arch/mips/dec/prom/prom.h by the Anonymous. + */ +@@ -97,6 +97,17 @@ extern int (*__pmax_close)(int); + + #ifdef CONFIG_64BIT + ++#define O32_STK_SIZE 512 ++extern unsigned long o32_stk[]; ++ ++/* Switch the stack if outside the 32-bit address space. */ ++static inline unsigned long *o32_get_stk(void) ++{ ++ long fp = (long)__builtin_frame_address(0); ++ ++ return fp != (int)fp ? o32_stk + O32_STK_SIZE : NULL; ++} ++ + /* + * On MIPS64 we have to call PROM functions via a helper + * dispatcher to accommodate ABI incompatibilities. +@@ -128,7 +139,7 @@ int __DEC_PROM_O32(_prom_printf, (int (* + + #define prom_getchar() _prom_getchar(__prom_getchar, NULL) + #define prom_getenv(x) _prom_getenv(__prom_getenv, NULL, x) +-#define prom_printf(x...) _prom_printf(__prom_printf, NULL, x) ++#define prom_printf(x...) _prom_printf(__prom_printf, o32_get_stk(), x) + + #else /* !CONFIG_64BIT */ + diff --git a/queue-7.1/mips-ip22-gio-fix-device-reference-leak-in-probe.patch b/queue-7.1/mips-ip22-gio-fix-device-reference-leak-in-probe.patch new file mode 100644 index 0000000000..3f604e2adb --- /dev/null +++ b/queue-7.1/mips-ip22-gio-fix-device-reference-leak-in-probe.patch @@ -0,0 +1,39 @@ +From b82930a4c5dbc5c4df39c0f93d968c239f2c6885 Mon Sep 17 00:00:00 2001 +From: Johan Hovold +Date: Fri, 24 Apr 2026 12:28:47 +0200 +Subject: MIPS: ip22-gio: fix device reference leak in probe + +From: Johan Hovold + +commit b82930a4c5dbc5c4df39c0f93d968c239f2c6885 upstream. + +The gio probe function needlessly takes a device reference which is +never released and therefore prevents unbound gio devices from being +freed. + +Fixes: e84de0c61905 ("MIPS: GIO bus support for SGI IP22/28") +Cc: stable@vger.kernel.org # 3.3 +Cc: Thomas Bogendoerfer +Signed-off-by: Johan Hovold +Signed-off-by: Thomas Bogendoerfer +Signed-off-by: Greg Kroah-Hartman +--- + arch/mips/sgi-ip22/ip22-gio.c | 4 ---- + 1 file changed, 4 deletions(-) + +--- a/arch/mips/sgi-ip22/ip22-gio.c ++++ b/arch/mips/sgi-ip22/ip22-gio.c +@@ -133,13 +133,9 @@ static int gio_device_probe(struct devic + if (!drv->probe) + return error; + +- gio_dev_get(gio_dev); +- + match = gio_match_device(drv->id_table, gio_dev); + if (match) + error = drv->probe(gio_dev, match); +- if (error) +- gio_dev_put(gio_dev); + + return error; + } diff --git a/queue-7.1/mips-ip22-gio-fix-gio-device-memory-leak.patch b/queue-7.1/mips-ip22-gio-fix-gio-device-memory-leak.patch new file mode 100644 index 0000000000..4504a0249e --- /dev/null +++ b/queue-7.1/mips-ip22-gio-fix-gio-device-memory-leak.patch @@ -0,0 +1,33 @@ +From 7de9a1b45f5a95b58145653c525c8fb80292d9ab Mon Sep 17 00:00:00 2001 +From: Johan Hovold +Date: Fri, 24 Apr 2026 12:28:46 +0200 +Subject: MIPS: ip22-gio: fix gio device memory leak + +From: Johan Hovold + +commit 7de9a1b45f5a95b58145653c525c8fb80292d9ab upstream. + +The gio device release callback was never wired up so gio devices are +not freed when the last reference is dropped. + +Fixes: e84de0c61905 ("MIPS: GIO bus support for SGI IP22/28") +Cc: stable@vger.kernel.org # 3.3 +Cc: Thomas Bogendoerfer +Signed-off-by: Johan Hovold +Signed-off-by: Thomas Bogendoerfer +Signed-off-by: Greg Kroah-Hartman +--- + arch/mips/sgi-ip22/ip22-gio.c | 2 ++ + 1 file changed, 2 insertions(+) + +--- a/arch/mips/sgi-ip22/ip22-gio.c ++++ b/arch/mips/sgi-ip22/ip22-gio.c +@@ -101,6 +101,8 @@ int gio_device_register(struct gio_devic + { + giodev->dev.bus = &gio_bus_type; + giodev->dev.parent = &gio_bus; ++ giodev->dev.release = gio_release_dev; ++ + return device_register(&giodev->dev); + } + EXPORT_SYMBOL_GPL(gio_device_register); diff --git a/queue-7.1/mips-ip22-gio-fix-kfree-of-static-object.patch b/queue-7.1/mips-ip22-gio-fix-kfree-of-static-object.patch new file mode 100644 index 0000000000..0a0589d752 --- /dev/null +++ b/queue-7.1/mips-ip22-gio-fix-kfree-of-static-object.patch @@ -0,0 +1,32 @@ +From c62cdd3e919bdf84c37ec46810f87cdb1736e822 Mon Sep 17 00:00:00 2001 +From: Johan Hovold +Date: Fri, 24 Apr 2026 12:28:45 +0200 +Subject: MIPS: ip22-gio: fix kfree() of static object + +From: Johan Hovold + +commit c62cdd3e919bdf84c37ec46810f87cdb1736e822 upstream. + +The gio bus root device is a statically allocated object which must not +be freed by kfree() on failure to register the device or bus. + +Fixes: 82242d28ff8b ("MIPS: IP22: Add missing put_device call") +Cc: stable@vger.kernel.org # 3.17 +Cc: Levente Kurusa +Signed-off-by: Johan Hovold +Signed-off-by: Thomas Bogendoerfer +Signed-off-by: Greg Kroah-Hartman +--- + arch/mips/sgi-ip22/ip22-gio.c | 1 - + 1 file changed, 1 deletion(-) + +--- a/arch/mips/sgi-ip22/ip22-gio.c ++++ b/arch/mips/sgi-ip22/ip22-gio.c +@@ -30,7 +30,6 @@ static struct { + + static void gio_bus_release(struct device *dev) + { +- kfree(dev); + } + + static struct device gio_bus = { diff --git a/queue-7.1/mips-sched-fix-cpumask_offstack-memory-corruption.patch b/queue-7.1/mips-sched-fix-cpumask_offstack-memory-corruption.patch new file mode 100644 index 0000000000..2bef3ccb9d --- /dev/null +++ b/queue-7.1/mips-sched-fix-cpumask_offstack-memory-corruption.patch @@ -0,0 +1,94 @@ +From 98e37db4a34d3af3fb2f4648295c25b5e40b20e3 Mon Sep 17 00:00:00 2001 +From: Aaron Tomlin +Date: Tue, 26 May 2026 10:16:51 -0400 +Subject: mips: sched: Fix CPUMASK_OFFSTACK memory corruption + +From: Aaron Tomlin + +commit 98e37db4a34d3af3fb2f4648295c25b5e40b20e3 upstream. + +This patch addresses a critical memory management flaw. When +CONFIG_CPUMASK_OFFSTACK is enabled, cpumask_var_t is a pointer. +Consequently, sizeof(new_mask) evaluates to the pointer size, causing +copy_from_user() to clobber the mask pointer. Furthermore, the old +logic performed copy_from_user() before allocating the mask. + +Fix this by allocating new_mask first. To handle variable-sized user +masks correctly, use cpumask_size() to truncate overly large user masks +or pad undersized masks with zeros before copying the data directly into +the allocated buffer. + +Fixes: 295cbf6d63165 ("[MIPS] Move FPU affinity code into separate file.") +Cc: stable@vger.kernel.org +Signed-off-by: Aaron Tomlin +Signed-off-by: Thomas Bogendoerfer +Signed-off-by: Greg Kroah-Hartman +--- + arch/mips/kernel/mips-mt-fpaff.c | 28 +++++++++++++++------------- + 1 file changed, 15 insertions(+), 13 deletions(-) + +--- a/arch/mips/kernel/mips-mt-fpaff.c ++++ b/arch/mips/kernel/mips-mt-fpaff.c +@@ -71,11 +71,16 @@ asmlinkage long mipsmt_sys_sched_setaffi + struct task_struct *p; + int retval; + +- if (len < sizeof(new_mask)) +- return -EINVAL; +- +- if (copy_from_user(&new_mask, user_mask_ptr, sizeof(new_mask))) +- return -EFAULT; ++ if (!alloc_cpumask_var(&new_mask, GFP_KERNEL)) ++ return -ENOMEM; ++ if (len < cpumask_size()) ++ cpumask_clear(new_mask); ++ else if (len > cpumask_size()) ++ len = cpumask_size(); ++ if (copy_from_user(new_mask, user_mask_ptr, len)) { ++ retval = -EFAULT; ++ goto out_free_new_mask; ++ } + + cpus_read_lock(); + rcu_read_lock(); +@@ -84,7 +89,8 @@ asmlinkage long mipsmt_sys_sched_setaffi + if (!p) { + rcu_read_unlock(); + cpus_read_unlock(); +- return -ESRCH; ++ retval = -ESRCH; ++ goto out_free_new_mask; + } + + /* Prevent p going away */ +@@ -95,13 +101,9 @@ asmlinkage long mipsmt_sys_sched_setaffi + retval = -ENOMEM; + goto out_put_task; + } +- if (!alloc_cpumask_var(&new_mask, GFP_KERNEL)) { +- retval = -ENOMEM; +- goto out_free_cpus_allowed; +- } + if (!alloc_cpumask_var(&effective_mask, GFP_KERNEL)) { + retval = -ENOMEM; +- goto out_free_new_mask; ++ goto out_free_cpus_allowed; + } + if (!check_same_owner(p) && !capable(CAP_SYS_NICE)) { + retval = -EPERM; +@@ -142,13 +144,13 @@ asmlinkage long mipsmt_sys_sched_setaffi + } + out_unlock: + free_cpumask_var(effective_mask); +-out_free_new_mask: +- free_cpumask_var(new_mask); + out_free_cpus_allowed: + free_cpumask_var(cpus_allowed); + out_put_task: + put_task_struct(p); + cpus_read_unlock(); ++out_free_new_mask: ++ free_cpumask_var(new_mask); + return retval; + } + diff --git a/queue-7.1/mm-damon-core-always-put-unsuccessfully-committed-target-pids.patch b/queue-7.1/mm-damon-core-always-put-unsuccessfully-committed-target-pids.patch new file mode 100644 index 0000000000..7ee373b562 --- /dev/null +++ b/queue-7.1/mm-damon-core-always-put-unsuccessfully-committed-target-pids.patch @@ -0,0 +1,161 @@ +From 6a66c557a2ab2609575bafd15e093669c05f9711 Mon Sep 17 00:00:00 2001 +From: SeongJae Park +Date: Thu, 4 Jun 2026 18:38:48 -0700 +Subject: mm/damon/core: always put unsuccessfully committed target pids + +From: SeongJae Park + +commit 6a66c557a2ab2609575bafd15e093669c05f9711 upstream. + +damon_commit_target() puts and gets the destination and the source target +pids. It puts the destination target pid because it will be overwritten +by the source target pid. It gets the source pid because the caller is +supposed to eventually put the pids. In more detail, the caller will call +damon_destroy_ctx() after damon_commit_ctx() to destroy the entire source +context. And in this case, [f]vaddr operation set's cleanup_target() +callback will put the pids. + +The commit operation is made at the context level. The operation can fail +in multiple places including in the middle and after the targets commit +operations. For any such failures, immediately the error is returned to +the damon_commit_ctx() caller. If some or all of the source target pids +were committed to the destination during the unsuccessful context commit +attempt, those pids should be put twice. + +The source context will do the put operations using the above explained +routine. However, let's suppose the destination context was not +originally using [f]vaddr operation set and the commit failed before the +ops of the source context is committed. The destination does not have the +cleanup_target() ops callback, so it cannot put the pids via the +damon_destroy_ctx(). + +As a result, the pids are leaked. The issue in the real world would be +not very common. The commit feature is for changing parameters of running +DAMON context while inheriting internal status like the monitoring +results. The monitoring results of a physical address range ain't have +things that are beneficial to be inherited to a virtual address ranges +monitoring. So the problem-causing DAMON control would be not very common +in the real world. That said, it is a supported feature. And +damon_commit_target() failure due to memory allocation is relatively +realistic [1] if there are a huge number of target regions. + +Fix by putting the pids in the commit operation in case of the failures. + +The issue was discovered [2] by Sashiko. + +Link: https://lore.kernel.org/20260605013849.83750-1-sj@kernel.org +Link: https://lore.kernel.org/20260603112306.58490-1-akinobu.mita@gmail.com [1] +Link: https://lore.kernel.org/20260320020056.835-1-sj@kernel.org [2] +Fixes: 83dc7bbaecae ("mm/damon/sysfs: use damon_commit_ctx()") +Signed-off-by: SeongJae Park +Cc: # 6.11.x +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + mm/damon/core.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++-------- + 1 file changed, 47 insertions(+), 8 deletions(-) + +--- a/mm/damon/core.c ++++ b/mm/damon/core.c +@@ -1257,10 +1257,36 @@ static int damon_commit_target( + return 0; + } + ++/* ++ * damon_revert_target_commits() - revert unsuccessful target commits. ++ * @dst: Commit destination context ++ * @failed: Commit failed destination target ++ * @src: Commit source context ++ * ++ * Revert target states that changed by damon_commit_target(), and cannot be ++ * cleaned up by the destination context's ops.cleanup_target(). ++ */ ++static void damon_revert_target_commits(struct damon_ctx *dst, ++ struct damon_target *failed, struct damon_ctx *src) ++{ ++ struct damon_target *target; ++ ++ if (!damon_target_has_pid(src)) ++ return; ++ if (dst->ops.cleanup_target) ++ return; ++ damon_for_each_target(target, dst) { ++ if (target == failed) ++ return; ++ put_pid(target->pid); ++ } ++} ++ + static int damon_commit_targets( + struct damon_ctx *dst, struct damon_ctx *src) + { + struct damon_target *dst_target, *next, *src_target, *new_target; ++ struct damon_target *failed; + int i = 0, j = 0, err; + + damon_for_each_target_safe(dst_target, next, dst) { +@@ -1274,8 +1300,10 @@ static int damon_commit_targets( + dst_target, damon_target_has_pid(dst), + src_target, damon_target_has_pid(src), + src->min_region_sz); +- if (err) +- return err; ++ if (err) { ++ failed = dst_target; ++ goto out; ++ } + } else { + struct damos *s; + +@@ -1289,25 +1317,34 @@ static int damon_commit_targets( + } + } + ++ failed = NULL; + damon_for_each_target_safe(src_target, next, src) { + if (j++ < i) + continue; + /* target to remove has no matching dst */ +- if (src_target->obsolete) +- return -EINVAL; ++ if (src_target->obsolete) { ++ err = -EINVAL; ++ goto out; ++ } + new_target = damon_new_target(); +- if (!new_target) +- return -ENOMEM; ++ if (!new_target) { ++ err = -ENOMEM; ++ goto out; ++ } + err = damon_commit_target(new_target, false, + src_target, damon_target_has_pid(src), + src->min_region_sz); + if (err) { + damon_destroy_target(new_target, NULL); +- return err; ++ goto out; + } + damon_add_target(dst, new_target); + } + return 0; ++ ++out: ++ damon_revert_target_commits(dst, failed, src); ++ return err; + } + + /** +@@ -1346,8 +1383,10 @@ int damon_commit_ctx(struct damon_ctx *d + */ + if (!damon_attrs_equals(&dst->attrs, &src->attrs)) { + err = damon_set_attrs(dst, &src->attrs); +- if (err) ++ if (err) { ++ damon_revert_target_commits(dst, NULL, src); + return err; ++ } + } + dst->ops = src->ops; + dst->addr_unit = src->addr_unit; diff --git a/queue-7.1/mm-damon-core-make-charge_addr_from-aware-of-end-address-exclusivity.patch b/queue-7.1/mm-damon-core-make-charge_addr_from-aware-of-end-address-exclusivity.patch new file mode 100644 index 0000000000..c1f7ab503c --- /dev/null +++ b/queue-7.1/mm-damon-core-make-charge_addr_from-aware-of-end-address-exclusivity.patch @@ -0,0 +1,40 @@ +From 5a2d162e22bf33eb89d53e802d0fc1ec422e19b6 Mon Sep 17 00:00:00 2001 +From: SeongJae Park +Date: Mon, 27 Apr 2026 21:29:40 -0700 +Subject: mm/damon/core: make charge_addr_from aware of end-address exclusivity + +From: SeongJae Park + +commit 5a2d162e22bf33eb89d53e802d0fc1ec422e19b6 upstream. + +DAMON region end address is exclusive one, but charge_addr_from is +assigned assuming the end address is inclusive. As a result, DAMOS action +to next up to min_region_sz memory can be skipped. This is quite +negligible user impact. But, the bug is a bug that can be very simply +fixed. Fix the wrong assignment to respect the exclusiveness of the +address. + +The issue was discovered [1] by Sashiko. + +Link: https://lore.kernel.org/20260428042942.118230-1-sj@kernel.org +Link: https://lore.kernel.org/20260428032324.115663-1-sj@kernel.org [1] +Fixes: 50585192bc2e ("mm/damon/schemes: skip already charged targets and regions") +Signed-off-by: SeongJae Park +Cc: # 5.16.x +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + mm/damon/core.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/mm/damon/core.c ++++ b/mm/damon/core.c +@@ -2106,7 +2106,7 @@ static void damos_apply_scheme(struct da + if (damos_quota_is_set(quota) && + quota->charged_sz >= quota->esz) { + quota->charge_target_from = t; +- quota->charge_addr_from = r->ar.end + 1; ++ quota->charge_addr_from = r->ar.end; + } + } + if (s->action != DAMOS_STAT) diff --git a/queue-7.1/mm-damon-sysfs-schemes-fix-dir-put-orders-in-access_pattern_add_dirs.patch b/queue-7.1/mm-damon-sysfs-schemes-fix-dir-put-orders-in-access_pattern_add_dirs.patch new file mode 100644 index 0000000000..dc4c252afd --- /dev/null +++ b/queue-7.1/mm-damon-sysfs-schemes-fix-dir-put-orders-in-access_pattern_add_dirs.patch @@ -0,0 +1,67 @@ +From d58fdbe37a829fd2e5803dd4e5a72992dd8c5368 Mon Sep 17 00:00:00 2001 +From: SeongJae Park +Date: Wed, 17 Jun 2026 17:56:47 -0700 +Subject: mm/damon/sysfs-schemes: fix dir put orders in access_pattern_add_dirs() + +From: SeongJae Park + +commit d58fdbe37a829fd2e5803dd4e5a72992dd8c5368 upstream. + +Patch series "mm/damon/sysfs-schemes: fix wrong directories put orders in +error paths". + +Error paths of damon_sysfs_access_pattern_add_dirs() and +damon_sysfs_scheme_add_dirs() functions put references to directories in +wrong orders. As a result, uninitialized memory dereference and/or +memory leak can happen. Fix those. + + +This patch (of 2): + +In access_pattern_add_dirs(), error handling path puts references starting +from setup failed directories. If the failure happpened from the initial +allication in the setup functions, uninitialized memory dereference +happen. The allocation failures will not commonly happen, but the +consequence is quite bad. Fix the wrong reference put orders. + +The issue was discovered [1] by Sashiko. + +Link: https://lore.kernel.org/20260618005650.83868-2-sj@kernel.org +Link: https://lore.kernel.org/20260617060005.86852-1-sj@kernel.org [1] +Fixes: 7e84b1f8212a ("mm/damon/sysfs: support DAMON-based Operation Schemes") +Signed-off-by: SeongJae Park +Cc: # 5.18.x +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + mm/damon/sysfs-schemes.c | 9 +++------ + 1 file changed, 3 insertions(+), 6 deletions(-) + +--- a/mm/damon/sysfs-schemes.c ++++ b/mm/damon/sysfs-schemes.c +@@ -1767,22 +1767,19 @@ static int damon_sysfs_access_pattern_ad + err = damon_sysfs_access_pattern_add_range_dir(access_pattern, + &access_pattern->sz, "sz"); + if (err) +- goto put_sz_out; ++ return err; + + err = damon_sysfs_access_pattern_add_range_dir(access_pattern, + &access_pattern->nr_accesses, "nr_accesses"); + if (err) +- goto put_nr_accesses_sz_out; ++ goto put_sz_out; + + err = damon_sysfs_access_pattern_add_range_dir(access_pattern, + &access_pattern->age, "age"); + if (err) +- goto put_age_nr_accesses_sz_out; ++ goto put_nr_accesses_sz_out; + return 0; + +-put_age_nr_accesses_sz_out: +- kobject_put(&access_pattern->age->kobj); +- access_pattern->age = NULL; + put_nr_accesses_sz_out: + kobject_put(&access_pattern->nr_accesses->kobj); + access_pattern->nr_accesses = NULL; diff --git a/queue-7.1/mm-damon-sysfs-schemes-put-stats-for-scheme_add_dirs-internal-error.patch b/queue-7.1/mm-damon-sysfs-schemes-put-stats-for-scheme_add_dirs-internal-error.patch new file mode 100644 index 0000000000..9f05805da8 --- /dev/null +++ b/queue-7.1/mm-damon-sysfs-schemes-put-stats-for-scheme_add_dirs-internal-error.patch @@ -0,0 +1,59 @@ +From 05ea83ee88ca70f8932906d9f2617ff996f45b50 Mon Sep 17 00:00:00 2001 +From: SeongJae Park +Date: Wed, 17 Jun 2026 17:56:48 -0700 +Subject: mm/damon/sysfs-schemes: put stats for scheme_add_dirs() internal error + +From: SeongJae Park + +commit 05ea83ee88ca70f8932906d9f2617ff996f45b50 upstream. + +damon_sysfs_scheme_add_dirs() setup the tried_regions directory after the +stats directory setup is completed. When the tried_regions directory +setup is failed, the setup function ensures the reference for the tried +regions directory is released. Hence the error path should put references +on setup succeeded directory objects, starting from the stats directory. +However, the error path is putting the tried_regions directory instead of +the stats directory. + +As a direct result, the stats directory object is leaked. Worse yet, if +the tried_regions directory setup failed from the initial allocation, the +scheme->tried_regions field remains uninitialized. The following +kobject_put(&scheme->tried_regions->kobj) call in the error path will +dereference the uninitialized memory. The setup failures should not be +common. But once it happens, the consequence is quite bad. + +Fix this issue by correctly putting the stats directory instead of the +tried_regions directory. + +The issue was discovered [1] by Sashiko. + +Link: https://lore.kernel.org/20260618005650.83868-3-sj@kernel.org +Link: https://lore.kernel.org/20260617005223.96813-1-sj@kernel.org [1] +Fixes: 5181b75f438d ("mm/damon/sysfs-schemes: implement schemes/tried_regions directory") +Signed-off-by: SeongJae Park +Cc: # 6.2.x +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + mm/damon/sysfs-schemes.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +--- a/mm/damon/sysfs-schemes.c ++++ b/mm/damon/sysfs-schemes.c +@@ -2283,12 +2283,12 @@ static int damon_sysfs_scheme_add_dirs(s + goto put_filters_watermarks_quotas_access_pattern_out; + err = damon_sysfs_scheme_set_tried_regions(scheme); + if (err) +- goto put_tried_regions_out; ++ goto put_stats_out; + return 0; + +-put_tried_regions_out: +- kobject_put(&scheme->tried_regions->kobj); +- scheme->tried_regions = NULL; ++put_stats_out: ++ kobject_put(&scheme->stats->kobj); ++ scheme->stats = NULL; + put_filters_watermarks_quotas_access_pattern_out: + kobject_put(&scheme->ops_filters->kobj); + scheme->ops_filters = NULL; diff --git a/queue-7.1/mm-huge_memory-preserve-pmd_swp_uffd_wp-on-device-private-pmd-downgrade.patch b/queue-7.1/mm-huge_memory-preserve-pmd_swp_uffd_wp-on-device-private-pmd-downgrade.patch new file mode 100644 index 0000000000..e9f525d39a --- /dev/null +++ b/queue-7.1/mm-huge_memory-preserve-pmd_swp_uffd_wp-on-device-private-pmd-downgrade.patch @@ -0,0 +1,46 @@ +From f7e2c21bd1f57cd5350eecdfdb5d6025ca6afbab Mon Sep 17 00:00:00 2001 +From: "Kiryl Shutsemau (Meta)" +Date: Fri, 29 May 2026 18:23:28 +0100 +Subject: mm/huge_memory: preserve pmd_swp_uffd_wp on device-private PMD downgrade + +From: Kiryl Shutsemau (Meta) + +commit f7e2c21bd1f57cd5350eecdfdb5d6025ca6afbab upstream. + +change_non_present_huge_pmd() rewrites a writable device-private PMD swap +entry into a readable one without carrying pmd_swp_uffd_wp() across. The +PTE-level change_softleaf_pte() does this correctly; mirror that here, +matching what copy_huge_pmd() does for the fork path. Without the carry, +a plain mprotect() over a UFFD_WP-marked device-private THP strips the bit +and the trap is bypassed on swap-in. + +Link: https://lore.kernel.org/20260529172331.356655-5-kas@kernel.org +Fixes: 368076f52ebe ("mm/huge_memory: add device-private THP support to PMD operations") +Signed-off-by: Kiryl Shutsemau +Reported-by: Sashiko AI review +Reviewed-by: Balbir Singh +Cc: David Hildenbrand +Cc: Lorenzo Stoakes +Cc: Michal Hocko +Cc: Mike Rapoport +Cc: Peter Xu +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + mm/huge_memory.c | 2 ++ + 1 file changed, 2 insertions(+) + +--- a/mm/huge_memory.c ++++ b/mm/huge_memory.c +@@ -2638,6 +2638,8 @@ static void change_non_present_huge_pmd( + } else if (softleaf_is_device_private_write(entry)) { + entry = make_readable_device_private_entry(swp_offset(entry)); + newpmd = swp_entry_to_pmd(entry); ++ if (pmd_swp_uffd_wp(*pmd)) ++ newpmd = pmd_swp_mkuffd_wp(newpmd); + } else { + newpmd = *pmd; + } diff --git a/queue-7.1/mm-hugetlb-fix-hugetlb-cgroup-rsvd-charge-uncharge-mismatch.patch b/queue-7.1/mm-hugetlb-fix-hugetlb-cgroup-rsvd-charge-uncharge-mismatch.patch new file mode 100644 index 0000000000..77d83c2d43 --- /dev/null +++ b/queue-7.1/mm-hugetlb-fix-hugetlb-cgroup-rsvd-charge-uncharge-mismatch.patch @@ -0,0 +1,84 @@ +From 15807d0ddde37407af72859426b654f3d1972b00 Mon Sep 17 00:00:00 2001 +From: Deepanshu Kartikey +Date: Sat, 28 Mar 2026 12:25:34 +0530 +Subject: mm/hugetlb: fix hugetlb cgroup rsvd charge/uncharge mismatch + +From: Deepanshu Kartikey + +commit 15807d0ddde37407af72859426b654f3d1972b00 upstream. + +In alloc_hugetlb_folio(), a single h_cg pointer is used for both the rsvd +and non-rsvd hugetlb cgroup charges. When map_chg is set, +hugetlb_cgroup_charge_cgroup_rsvd() stores the charged cgroup in h_cg, but +the immediately following hugetlb_cgroup_charge_cgroup() overwrites h_cg +with the non-rsvd cgroup pointer. + +As a result, hugetlb_cgroup_commit_charge_rsvd() stores the wrong +(non-rsvd) cgroup pointer into the folio's rsvd slot. + +When the folio is later freed, free_huge_folio() unconditionally calls +both hugetlb_cgroup_uncharge_folio() and +hugetlb_cgroup_uncharge_folio_rsvd(). The rsvd uncharge reads back the +wrong cgroup from the folio and decrements a counter that was never +charged for that cgroup, causing a page_counter underflow: + + page_counter underflow: -512 nr_pages=512 + WARNING: mm/page_counter.c:61 at page_counter_cancel + +Fix this by introducing a separate h_cg_rsvd pointer exclusively for the +rsvd charge path, keeping the rsvd and non-rsvd charges fully independent +through their charge, commit, and error uncharge paths. + +Link: https://lore.kernel.org/20260328065534.346053-1-kartikey406@gmail.com +Fixes: 08cf9faf7558 ("hugetlb_cgroup: support noreserve mappings") +Reported-by: syzbot+226c1f947186f8fef796@syzkaller.appspotmail.com +Closes: https://syzkaller.appspot.com/bug?extid=226c1f947186f8fef796 +Signed-off-by: Deepanshu Kartikey +Reviewed-by: Muchun Song +Cc: David Hildenbrand +Cc: Oscar Salvador +Cc: Mina Almasry +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + mm/hugetlb.c | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +--- a/mm/hugetlb.c ++++ b/mm/hugetlb.c +@@ -2862,6 +2862,7 @@ struct folio *alloc_hugetlb_folio(struct + map_chg_state map_chg; + int ret, idx; + struct hugetlb_cgroup *h_cg = NULL; ++ struct hugetlb_cgroup *h_cg_rsvd = NULL; + gfp_t gfp = htlb_alloc_mask(h) | __GFP_RETRY_MAYFAIL; + + idx = hstate_index(h); +@@ -2912,7 +2913,7 @@ struct folio *alloc_hugetlb_folio(struct + */ + if (map_chg) { + ret = hugetlb_cgroup_charge_cgroup_rsvd( +- idx, pages_per_huge_page(h), &h_cg); ++ idx, pages_per_huge_page(h), &h_cg_rsvd); + if (ret) + goto out_subpool_put; + } +@@ -2954,7 +2955,7 @@ struct folio *alloc_hugetlb_folio(struct + */ + if (map_chg) { + hugetlb_cgroup_commit_charge_rsvd(idx, pages_per_huge_page(h), +- h_cg, folio); ++ h_cg_rsvd, folio); + } + + spin_unlock_irq(&hugetlb_lock); +@@ -3006,7 +3007,7 @@ out_uncharge_cgroup: + out_uncharge_cgroup_reservation: + if (map_chg) + hugetlb_cgroup_uncharge_cgroup_rsvd(idx, pages_per_huge_page(h), +- h_cg); ++ h_cg_rsvd); + out_subpool_put: + /* + * put page to subpool iff the quota of subpool's rsv_hpages is used diff --git a/queue-7.1/mm-memory_hotplug-fix-incorrect-altmap-passing-in-error-path.patch b/queue-7.1/mm-memory_hotplug-fix-incorrect-altmap-passing-in-error-path.patch new file mode 100644 index 0000000000..6f9bc723ab --- /dev/null +++ b/queue-7.1/mm-memory_hotplug-fix-incorrect-altmap-passing-in-error-path.patch @@ -0,0 +1,61 @@ +From 2fac4afa0e2e68841334c78c1821e49f74fbc66a Mon Sep 17 00:00:00 2001 +From: Muchun Song +Date: Tue, 28 Apr 2026 16:18:51 +0800 +Subject: mm/memory_hotplug: fix incorrect altmap passing in error path + +From: Muchun Song + +commit 2fac4afa0e2e68841334c78c1821e49f74fbc66a upstream. + +In create_altmaps_and_memory_blocks(), when arch_add_memory() succeeds +with memmap_on_memory enabled, the vmemmap pages are allocated from +params.altmap. If create_memory_block_devices() subsequently fails, the +error path calls arch_remove_memory() with a NULL altmap instead of +params.altmap. + +This is a bug that could lead to memory corruption. Since altmap is NULL, +vmemmap_free() falls back to freeing the vmemmap pages into the system +buddy allocator via free_pages() instead of the altmap. +arch_remove_memory() then immediately destroys the physical linear mapping +for this memory. This injects unowned pages into the buddy allocator, +causing machine checks or memory corruption if the system later attempts +to allocate and use those freed pages. + +Fix this by passing params.altmap to arch_remove_memory() in the error +path. + +Link: https://lore.kernel.org/20260428081855.1249045-3-songmuchun@bytedance.com +Fixes: 6b8f0798b85a ("mm/memory_hotplug: split memmap_on_memory requests across memblocks") +Signed-off-by: Muchun Song +Acked-by: David Hildenbrand (Arm) +Acked-by: Liam R. Howlett +Reviewed-by: Georgi Djakov +Cc: "Aneesh Kumar K.V" +Cc: Joao Martins +Cc: Lorenzo Stoakes +Cc: Madhavan Srinivasan +Cc: Michael Ellerman +Cc: Michal Hocko +Cc: Mike Rapoport (Microsoft) +Cc: Nicholas Piggin +Cc: Oscar Salvador +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + mm/memory_hotplug.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/mm/memory_hotplug.c ++++ b/mm/memory_hotplug.c +@@ -1470,7 +1470,7 @@ static int create_altmaps_and_memory_blo + ret = create_memory_block_devices(cur_start, memblock_size, nid, + params.altmap, group); + if (ret) { +- arch_remove_memory(cur_start, memblock_size, NULL); ++ arch_remove_memory(cur_start, memblock_size, params.altmap); + kfree(params.altmap); + goto out; + } diff --git a/queue-7.1/mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch b/queue-7.1/mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch new file mode 100644 index 0000000000..97df43348f --- /dev/null +++ b/queue-7.1/mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch @@ -0,0 +1,111 @@ +From 94405c6136839f7c462249c8b4b957bcb9527a9d Mon Sep 17 00:00:00 2001 +From: Muchun Song +Date: Tue, 28 Apr 2026 16:18:54 +0800 +Subject: mm/mm_init: fix pageblock migratetype for ZONE_DEVICE compound pages + +From: Muchun Song + +commit 94405c6136839f7c462249c8b4b957bcb9527a9d upstream. + +The memmap_init_zone_device() function only initializes the migratetype of +the first pageblock of a compound page. If the compound page size exceeds +pageblock_nr_pages (e.g., 1GB hugepages with 2MB pageblocks), subsequent +pageblocks in the compound page remain uninitialized. + +Move the migratetype initialization out of __init_zone_device_page() and +into a separate pageblock_migratetype_init_range() function. This +iterates over the entire PFN range of the memory, ensuring that all +pageblocks are correctly initialized. + +Also remove the stale confusing comment about MEMINIT_HOTPLUG above the +migratetype setting since it is an obsolete relic from commit 966cf44f637e +("mm: defer ZONE_DEVICE page initialization to the point where we init +pgmap") and no longer makes sense here. + +Link: https://lore.kernel.org/20260428081855.1249045-6-songmuchun@bytedance.com +Fixes: c4386bd8ee3a ("mm/memremap: add ZONE_DEVICE support for compound pages") +Signed-off-by: Muchun Song +Reviewed-by: Mike Rapoport (Microsoft) +Reviewed-by: Oscar Salvador +Acked-by: David Hildenbrand (Arm) +Acked-by: Liam R. Howlett +Cc: "Aneesh Kumar K.V" +Cc: Joao Martins +Cc: Lorenzo Stoakes +Cc: Madhavan Srinivasan +Cc: Michael Ellerman +Cc: Michal Hocko +Cc: Nicholas Piggin +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + mm/mm_init.c | 34 +++++++++++++++++++--------------- + 1 file changed, 19 insertions(+), 15 deletions(-) + +--- a/mm/mm_init.c ++++ b/mm/mm_init.c +@@ -674,6 +674,20 @@ static inline void fixup_hashdist(void) + static inline void fixup_hashdist(void) {} + #endif /* CONFIG_NUMA */ + ++#ifdef CONFIG_ZONE_DEVICE ++static __meminit void pageblock_migratetype_init_range(unsigned long pfn, ++ unsigned long nr_pages, int migratetype) ++{ ++ const unsigned long end = pfn + nr_pages; ++ ++ for (pfn = pageblock_align(pfn); pfn < end; pfn += pageblock_nr_pages) { ++ init_pageblock_migratetype(pfn_to_page(pfn), migratetype, false); ++ if (IS_ALIGNED(pfn, PAGES_PER_SECTION)) ++ cond_resched(); ++ } ++} ++#endif ++ + /* + * Initialize a reserved page unconditionally, finding its zone first. + */ +@@ -1012,21 +1026,6 @@ static void __ref __init_zone_device_pag + page->zone_device_data = NULL; + + /* +- * Mark the block movable so that blocks are reserved for +- * movable at startup. This will force kernel allocations +- * to reserve their blocks rather than leaking throughout +- * the address space during boot when many long-lived +- * kernel allocations are made. +- * +- * Please note that MEMINIT_HOTPLUG path doesn't clear memmap +- * because this is done early in section_activate() +- */ +- if (pageblock_aligned(pfn)) { +- init_pageblock_migratetype(page, MIGRATE_MOVABLE, false); +- cond_resched(); +- } +- +- /* + * ZONE_DEVICE pages other than MEMORY_TYPE_GENERIC are released + * directly to the driver page allocator which will set the page count + * to 1 when allocating the page. +@@ -1122,6 +1121,9 @@ void __ref memmap_init_zone_device(struc + + __init_zone_device_page(page, pfn, zone_idx, nid, pgmap); + ++ if (IS_ALIGNED(pfn, PAGES_PER_SECTION)) ++ cond_resched(); ++ + if (pfns_per_compound == 1) + continue; + +@@ -1129,6 +1131,8 @@ void __ref memmap_init_zone_device(struc + compound_nr_pages(altmap, pgmap)); + } + ++ pageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE); ++ + pr_debug("%s initialised %lu pages in %ums\n", __func__, + nr_pages, jiffies_to_msecs(jiffies - start)); + } diff --git a/queue-7.1/mm-mm_init-fix-uninitialized-struct-pages-for-zone_device.patch b/queue-7.1/mm-mm_init-fix-uninitialized-struct-pages-for-zone_device.patch new file mode 100644 index 0000000000..24b3245118 --- /dev/null +++ b/queue-7.1/mm-mm_init-fix-uninitialized-struct-pages-for-zone_device.patch @@ -0,0 +1,73 @@ +From cd681403a87085562499d60325b7b45d3be11217 Mon Sep 17 00:00:00 2001 +From: Muchun Song +Date: Tue, 28 Apr 2026 16:18:55 +0800 +Subject: mm/mm_init: fix uninitialized struct pages for ZONE_DEVICE + +From: Muchun Song + +commit cd681403a87085562499d60325b7b45d3be11217 upstream. + +If DAX memory is hotplugged into an unoccupied subsection of an early +section, section_activate() reuses the unoptimized boot memmap. However, +compound_nr_pages() still assumes that vmemmap optimization is in effect +and initializes only the reduced number of struct pages. As a result, the +remaining tail struct pages are left uninitialized, which can later lead +to unexpected behavior or crashes. + +Fix this by treating early sections as unoptimized when calculating how +many struct pages to initialize. + +Link: https://lore.kernel.org/20260428081855.1249045-7-songmuchun@bytedance.com +Fixes: 6fd3620b3428 ("mm/page_alloc: reuse tail struct pages for compound devmaps") +Signed-off-by: Muchun Song +Acked-by: David Hildenbrand (Arm) +Acked-by: Mike Rapoport (Microsoft) +Acked-by: Liam R. Howlett +Cc: "Aneesh Kumar K.V" +Cc: Joao Martins +Cc: Lorenzo Stoakes +Cc: Madhavan Srinivasan +Cc: Michael Ellerman +Cc: Michal Hocko +Cc: Nicholas Piggin +Cc: Oscar Salvador +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + mm/mm_init.c | 13 ++++++++++--- + 1 file changed, 10 insertions(+), 3 deletions(-) + +--- a/mm/mm_init.c ++++ b/mm/mm_init.c +@@ -1055,10 +1055,17 @@ static void __ref __init_zone_device_pag + * of how the sparse_vmemmap internals handle compound pages in the lack + * of an altmap. See vmemmap_populate_compound_pages(). + */ +-static inline unsigned long compound_nr_pages(struct vmem_altmap *altmap, ++static inline unsigned long compound_nr_pages(unsigned long pfn, ++ struct vmem_altmap *altmap, + struct dev_pagemap *pgmap) + { +- if (!vmemmap_can_optimize(altmap, pgmap)) ++ /* ++ * If DAX memory is hot-plugged into an unoccupied subsection ++ * of an early section, the unoptimized boot memmap is reused. ++ * See section_activate(). ++ */ ++ if (early_section(__pfn_to_section(pfn)) || ++ !vmemmap_can_optimize(altmap, pgmap)) + return pgmap_vmemmap_nr(pgmap); + + return VMEMMAP_RESERVE_NR * (PAGE_SIZE / sizeof(struct page)); +@@ -1128,7 +1135,7 @@ void __ref memmap_init_zone_device(struc + continue; + + memmap_init_compound(page, pfn, zone_idx, nid, pgmap, +- compound_nr_pages(altmap, pgmap)); ++ compound_nr_pages(pfn, altmap, pgmap)); + } + + pageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE); diff --git a/queue-7.1/mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch b/queue-7.1/mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch new file mode 100644 index 0000000000..a31272c1be --- /dev/null +++ b/queue-7.1/mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch @@ -0,0 +1,137 @@ +From c373f7f98e6ad591c85d40548cf8b6443be69311 Mon Sep 17 00:00:00 2001 +From: Muchun Song +Date: Tue, 28 Apr 2026 16:18:50 +0800 +Subject: mm/sparse-vmemmap: fix vmemmap accounting underflow + +From: Muchun Song + +commit c373f7f98e6ad591c85d40548cf8b6443be69311 upstream. + +Patch series "mm: Fix vmemmap optimization accounting and initialization", +v8. + +The series fixes several bugs in vmemmap optimization, mainly around +incorrect page accounting and memmap initialization in DAX and memory +hotplug paths. It also fixes pageblock migratetype initialization and +struct page initialization for ZONE_DEVICE compound pages. + +Patches 1-4 fix vmemmap accounting issues. Patch 1 fixes an accounting +underflow in the section activation failure path by moving vmemmap page +accounting into the lower-level allocation and freeing helpers. Patch 2 +fixes incorrect altmap passing in the memory hotplug error path. Patch 3 +passes pgmap through memory deactivation paths so the teardown side can +determine whether vmemmap optimization was in effect. Patch 4 uses that +information to account the optimized DAX vmemmap size correctly. + +Patches 5-6 fix initialization issues in mm/mm_init. One makes sure all +pageblocks in ZONE_DEVICE compound pages get their migratetype +initialized. The other fixes a case where DAX memory hotplug reuses an +unoptimized early-section memmap while compound_nr_pages() still assumes +vmemmap optimization, leaving tail struct pages uninitialized. + + +This patch (of 6): + +In section_activate(), if populate_section_memmap() fails, the error +handling path calls section_deactivate() to roll back the state. This +causes a vmemmap accounting imbalance. + +Since commit c3576889d87b ("mm: fix accounting of memmap pages"), memmap +pages are accounted for only after populate_section_memmap() succeeds. +However, the failure path unconditionally calls section_deactivate(), +which decreases the vmemmap count. Consequently, a failure in +populate_section_memmap() leads to an accounting underflow, incorrectly +reducing the system's tracked vmemmap usage. + +Fix this more thoroughly by moving all accounting calls into the lower +level functions that actually perform the vmemmap allocation and freeing: + + - populate_section_memmap() accounts for newly allocated vmemmap pages - +depopulate_section_memmap() unaccounts when vmemmap is freed + +This ensures proper accounting in all code paths, including error handling +and early section cases. + +Link: https://lore.kernel.org/20260428081855.1249045-1-songmuchun@bytedance.com +Link: https://lore.kernel.org/20260428081855.1249045-2-songmuchun@bytedance.com +Fixes: c3576889d87b ("mm: fix accounting of memmap pages") +Signed-off-by: Muchun Song +Acked-by: Mike Rapoport (Microsoft) +Acked-by: Oscar Salvador +Acked-by: David Hildenbrand (Arm) +Acked-by: Liam R. Howlett +Cc: "Aneesh Kumar K.V" +Cc: Joao Martins +Cc: Lorenzo Stoakes +Cc: Madhavan Srinivasan +Cc: Michael Ellerman +Cc: Michal Hocko +Cc: Nicholas Piggin +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + mm/sparse-vmemmap.c | 20 ++++++++++++-------- + 1 file changed, 12 insertions(+), 8 deletions(-) + +--- a/mm/sparse-vmemmap.c ++++ b/mm/sparse-vmemmap.c +@@ -656,7 +656,12 @@ static struct page * __meminit populate_ + unsigned long nr_pages, int nid, struct vmem_altmap *altmap, + struct dev_pagemap *pgmap) + { +- return __populate_section_memmap(pfn, nr_pages, nid, altmap, pgmap); ++ struct page *page = __populate_section_memmap(pfn, nr_pages, nid, altmap, ++ pgmap); ++ ++ memmap_pages_add(DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE)); ++ ++ return page; + } + + static void depopulate_section_memmap(unsigned long pfn, unsigned long nr_pages, +@@ -665,13 +670,17 @@ static void depopulate_section_memmap(un + unsigned long start = (unsigned long) pfn_to_page(pfn); + unsigned long end = start + nr_pages * sizeof(struct page); + ++ memmap_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE))); + vmemmap_free(start, end, altmap); + } ++ + static void free_map_bootmem(struct page *memmap) + { + unsigned long start = (unsigned long)memmap; + unsigned long end = (unsigned long)(memmap + PAGES_PER_SECTION); + ++ memmap_boot_pages_add(-1L * (DIV_ROUND_UP(PAGES_PER_SECTION * sizeof(struct page), ++ PAGE_SIZE))); + vmemmap_free(start, end, NULL); + } + +@@ -774,14 +783,10 @@ static void section_deactivate(unsigned + * The memmap of early sections is always fully populated. See + * section_activate() and pfn_valid() . + */ +- if (!section_is_early) { +- memmap_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE))); ++ if (!section_is_early) + depopulate_section_memmap(pfn, nr_pages, altmap); +- } else if (memmap) { +- memmap_boot_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), +- PAGE_SIZE))); ++ else if (memmap) + free_map_bootmem(memmap); +- } + + if (empty) + ms->section_mem_map = (unsigned long)NULL; +@@ -826,7 +831,6 @@ static struct page * __meminit section_a + section_deactivate(pfn, nr_pages, altmap); + return ERR_PTR(-ENOMEM); + } +- memmap_pages_add(DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE)); + + return memmap; + } diff --git a/queue-7.1/mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch b/queue-7.1/mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch new file mode 100644 index 0000000000..d230d57899 --- /dev/null +++ b/queue-7.1/mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch @@ -0,0 +1,52 @@ +From 79d1661502c6e4b6f626185cef72cf2fa78116e1 Mon Sep 17 00:00:00 2001 +From: Florian Fuchs +Date: Mon, 18 May 2026 13:45:20 +0200 +Subject: mtd: maps: vmu-flash: fix fault in unaligned fixup + +From: Florian Fuchs + +commit 79d1661502c6e4b6f626185cef72cf2fa78116e1 upstream. + +Use kzalloc_obj() / kzalloc_objs() to allocate the memcard structs, +instead of kmalloc_obj() / kmalloc_objs() to prevent access to +uninitialized data. + +Fixes runtime error: Fault in unaligned fixup: 0000 [#1] at +mtd_get_fact_prot_info. + +Fixes: 47a72688fae7 ("mtd: flash mapping support for Dreamcast VMU.") +Cc: stable@vger.kernel.org +Signed-off-by: Florian Fuchs +Signed-off-by: Miquel Raynal +Signed-off-by: Greg Kroah-Hartman +--- + drivers/mtd/maps/vmu-flash.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +--- a/drivers/mtd/maps/vmu-flash.c ++++ b/drivers/mtd/maps/vmu-flash.c +@@ -609,7 +609,7 @@ static int vmu_connect(struct maple_devi + + basic_flash_data = be32_to_cpu(mdev->devinfo.function_data[c - 1]); + +- card = kmalloc_obj(struct memcard); ++ card = kzalloc_obj(struct memcard); + if (!card) { + error = -ENOMEM; + goto fail_nomem; +@@ -627,13 +627,13 @@ static int vmu_connect(struct maple_devi + * Not sure there are actually any multi-partition devices in the + * real world, but the hardware supports them, so, so will we + */ +- card->parts = kmalloc_objs(struct vmupart, card->partitions); ++ card->parts = kzalloc_objs(struct vmupart, card->partitions); + if (!card->parts) { + error = -ENOMEM; + goto fail_partitions; + } + +- card->mtd = kmalloc_objs(struct mtd_info, card->partitions); ++ card->mtd = kzalloc_objs(struct mtd_info, card->partitions); + if (!card->mtd) { + error = -ENOMEM; + goto fail_mtd_info; diff --git a/queue-7.1/mtd-rawnand-fix-condition-in-nand_select_target.patch b/queue-7.1/mtd-rawnand-fix-condition-in-nand_select_target.patch new file mode 100644 index 0000000000..60e4c2ff92 --- /dev/null +++ b/queue-7.1/mtd-rawnand-fix-condition-in-nand_select_target.patch @@ -0,0 +1,31 @@ +From 8507c2cc9e4fa402401819f44d1e8a5ef4d11d8b Mon Sep 17 00:00:00 2001 +From: Arseniy Krasnov +Date: Tue, 5 May 2026 11:30:30 +0300 +Subject: mtd: rawnand: fix condition in 'nand_select_target()' + +From: Arseniy Krasnov + +commit 8507c2cc9e4fa402401819f44d1e8a5ef4d11d8b upstream. + +'cs' here must be in range [0:nanddev_ntargets[. + +Cc: stable@vger.kernel.org +Fixes: 32813e288414 ("mtd: rawnand: Get rid of chip->numchips") +Signed-off-by: Arseniy Krasnov +Signed-off-by: Miquel Raynal +Signed-off-by: Greg Kroah-Hartman +--- + drivers/mtd/nand/raw/nand_base.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/mtd/nand/raw/nand_base.c ++++ b/drivers/mtd/nand/raw/nand_base.c +@@ -175,7 +175,7 @@ void nand_select_target(struct nand_chip + * cs should always lie between 0 and nanddev_ntargets(), when that's + * not the case it's a bug and the caller should be fixed. + */ +- if (WARN_ON(cs > nanddev_ntargets(&chip->base))) ++ if (WARN_ON(cs >= nanddev_ntargets(&chip->base))) + return; + + chip->cur_cs = cs; diff --git a/queue-7.1/mtd-rawnand-pl353-fix-probe-resource-allocation.patch b/queue-7.1/mtd-rawnand-pl353-fix-probe-resource-allocation.patch new file mode 100644 index 0000000000..8a77f8a214 --- /dev/null +++ b/queue-7.1/mtd-rawnand-pl353-fix-probe-resource-allocation.patch @@ -0,0 +1,35 @@ +From 19ed11aee966d91beebdef9d32ce926474872f79 Mon Sep 17 00:00:00 2001 +From: Bastien Curutchet +Date: Tue, 26 May 2026 09:10:00 +0200 +Subject: mtd: rawnand: pl353: fix probe resource allocation + +From: Bastien Curutchet + +commit 19ed11aee966d91beebdef9d32ce926474872f79 upstream. + +During probe(), the devm_ioremap() is called with the parent device +instead of the current one. So when the module is unloaded, the register +area isn't released. + +Target the pl35x device in the devm_ioremap() instead of its parent. + +Cc: stable@vger.kernel.org +Fixes: 08d8c62164a3 ("mtd: rawnand: pl353: Add support for the ARM PL353 SMC NAND controller") +Signed-off-by: Bastien Curutchet +Signed-off-by: Miquel Raynal +Signed-off-by: Greg Kroah-Hartman +--- + drivers/mtd/nand/raw/pl35x-nand-controller.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/mtd/nand/raw/pl35x-nand-controller.c ++++ b/drivers/mtd/nand/raw/pl35x-nand-controller.c +@@ -1155,7 +1155,7 @@ static int pl35x_nand_probe(struct platf + nfc->controller.ops = &pl35x_nandc_ops; + INIT_LIST_HEAD(&nfc->chips); + +- nfc->conf_regs = devm_ioremap_resource(&smc_amba->dev, &smc_amba->res); ++ nfc->conf_regs = devm_ioremap_resource(nfc->dev, &smc_amba->res); + if (IS_ERR(nfc->conf_regs)) + return PTR_ERR(nfc->conf_regs); + diff --git a/queue-7.1/mtd-slram-remove-failed-entries-from-the-device-list.patch b/queue-7.1/mtd-slram-remove-failed-entries-from-the-device-list.patch new file mode 100644 index 0000000000..a7719099d8 --- /dev/null +++ b/queue-7.1/mtd-slram-remove-failed-entries-from-the-device-list.patch @@ -0,0 +1,85 @@ +From 36f1648644d769c496a8e47e53603e863e358d73 Mon Sep 17 00:00:00 2001 +From: Ruoyu Wang +Date: Tue, 9 Jun 2026 16:45:27 +0800 +Subject: mtd: slram: remove failed entries from the device list + +From: Ruoyu Wang + +commit 36f1648644d769c496a8e47e53603e863e358d73 upstream. + +register_device() links a new slram_mtdlist entry before allocating all +of the state needed by the entry. If a later allocation, memremap(), or +mtd_device_register() fails, the partially initialized entry remains on +the global list. A later cleanup can then dereference or free invalid +state from that failed entry. + +Unwind the partially initialized entry and clear the list tail on each +failure path after the entry has been linked. + +Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") +Cc: stable@vger.kernel.org +Signed-off-by: Ruoyu Wang +Signed-off-by: Miquel Raynal +Signed-off-by: Greg Kroah-Hartman +--- + drivers/mtd/devices/slram.c | 22 ++++++++++++++++------ + 1 file changed, 16 insertions(+), 6 deletions(-) + +--- a/drivers/mtd/devices/slram.c ++++ b/drivers/mtd/devices/slram.c +@@ -129,6 +129,7 @@ static int slram_write(struct mtd_info * + static int register_device(char *name, unsigned long start, unsigned long length) + { + slram_mtd_list_t **curmtd; ++ int ret = -ENOMEM; + + curmtd = &slram_mtdlist; + while (*curmtd) { +@@ -155,14 +156,15 @@ static int register_device(char *name, u + + if (!(*curmtd)->mtdinfo) { + E("slram: Cannot allocate new MTD device.\n"); +- return(-ENOMEM); ++ goto err_free_list; + } + + if (!(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start = + memremap(start, length, + MEMREMAP_WB | MEMREMAP_WT | MEMREMAP_WC))) { + E("slram: memremap failed\n"); +- return -EIO; ++ ret = -EIO; ++ goto err_free_priv; + } + ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end = + ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start + length; +@@ -183,10 +185,8 @@ static int register_device(char *name, u + + if (mtd_device_register((*curmtd)->mtdinfo, NULL, 0)) { + E("slram: Failed to register new device\n"); +- memunmap(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start); +- kfree((*curmtd)->mtdinfo->priv); +- kfree((*curmtd)->mtdinfo); +- return(-EAGAIN); ++ ret = -EAGAIN; ++ goto err_unmap; + } + T("slram: Registered device %s from %luKiB to %luKiB\n", name, + (start / 1024), ((start + length) / 1024)); +@@ -194,6 +194,16 @@ static int register_device(char *name, u + ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start, + ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end); + return(0); ++ ++err_unmap: ++ memunmap(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start); ++err_free_priv: ++ kfree((*curmtd)->mtdinfo->priv); ++err_free_list: ++ kfree((*curmtd)->mtdinfo); ++ kfree(*curmtd); ++ *curmtd = NULL; ++ return ret; + } + + static void unregister_devices(void) diff --git a/queue-7.1/net-9p-fix-infinite-loop-in-p9_client_rpc-on-fatal-signal.patch b/queue-7.1/net-9p-fix-infinite-loop-in-p9_client_rpc-on-fatal-signal.patch new file mode 100644 index 0000000000..57eb6b1d76 --- /dev/null +++ b/queue-7.1/net-9p-fix-infinite-loop-in-p9_client_rpc-on-fatal-signal.patch @@ -0,0 +1,101 @@ +From 6b4f48728faa8bb514368f7eacda05565dea8696 Mon Sep 17 00:00:00 2001 +From: Vasiliy Kovalev +Date: Wed, 15 Apr 2026 18:52:37 +0300 +Subject: net/9p: fix infinite loop in p9_client_rpc on fatal signal + +From: Vasiliy Kovalev + +commit 6b4f48728faa8bb514368f7eacda05565dea8696 upstream. + +When p9_client_rpc() is called with type P9_TFLUSH and the transport +has no peer (e.g. fd transport backed by pipes with no 9p server), +a fatal signal causes an infinite loop: + + again: + err = io_wait_event_killable(req->wq, ...) + /* SIGKILL wakes the task, returns -ERESTARTSYS */ + + if (err == -ERESTARTSYS && c->status == Connected && + type == P9_TFLUSH) { + sigpending = 1; + clear_thread_flag(TIF_SIGPENDING); + goto again; + } + +clear_thread_flag() clears TIF_SIGPENDING before jumping back to +io_wait_event_killable(). signal_pending_state() checks TIF_SIGPENDING, +finds it zero, and the task goes to sleep again. The task can only wake +on the next signal delivery that calls signal_wake_up() and sets +TIF_SIGPENDING again. When that happens the loop repeats, clears +TIF_SIGPENDING, and sleeps again indefinitely. + +This is triggered in practice by coredump_wait(): when a thread in a +multi-threaded process causes a coredump (e.g. via SIGSYS from Syscall +User Dispatch), coredump_wait() sends SIGKILL to all other threads and +waits for them to call mm_release(). If one of those threads is blocked +in p9_client_rpc() over an fd transport with no peer, it enters the +P9_TFLUSH loop and never calls mm_release(), so coredump_wait() stalls +forever: + +INFO: task syz.0.18:676 blocked for more than 143 seconds. + Not tainted 6.12.77+ #1 +task:syz.0.18 state:D stack:27600 pid:676 tgid:673 ppid:630 flags:0x00000004 +Call Trace: + + context_switch kernel/sched/core.c:5344 [inline] + __schedule+0xcb4/0x5d50 kernel/sched/core.c:6724 + __schedule_loop kernel/sched/core.c:6801 [inline] + schedule+0xe5/0x350 kernel/sched/core.c:6816 + schedule_timeout+0x253/0x290 kernel/time/timer.c:2593 + do_wait_for_common kernel/sched/completion.c:95 [inline] + __wait_for_common+0x409/0x600 kernel/sched/completion.c:116 + wait_for_common kernel/sched/completion.c:127 [inline] + wait_for_completion_state+0x1d/0x40 kernel/sched/completion.c:264 + coredump_wait fs/coredump.c:448 [inline] + do_coredump+0x854/0x4350 fs/coredump.c:629 + get_signal+0x1425/0x2730 kernel/signal.c:2903 + arch_do_signal_or_restart+0x81/0x880 arch/x86/kernel/signal.c:337 + exit_to_user_mode_loop kernel/entry/common.c:111 [inline] + exit_to_user_mode_prepare include/linux/entry-common.h:328 [inline] + __syscall_exit_to_user_mode_work kernel/entry/common.c:207 [inline] + syscall_exit_to_user_mode+0xf9/0x160 kernel/entry/common.c:218 + do_syscall_64+0x102/0x220 arch/x86/entry/common.c:84 + entry_SYSCALL_64_after_hwframe+0x77/0x7f + + +Fix: check fatal_signal_pending() before clearing TIF_SIGPENDING in the +P9_TFLUSH retry loop. At that point TIF_SIGPENDING is still set, so +fatal_signal_pending() works correctly. If a fatal signal is pending, +jump to recalc_sigpending to restore TIF_SIGPENDING and return +-ERESTARTSYS to the caller. + +The same defect is present in stable kernels back to 5.4. On those +kernels the infinite loop is broken earlier by a second SIGKILL from +the parent process (e.g. kill_and_wait() retrying after a timeout), +resulting in a zombie process and a shutdown delay rather than a +permanent D-state hang, but the underlying flaw is the same. + +Found by Linux Verification Center (linuxtesting.org) with Syzkaller. + +Fixes: 91b8534fa8f5 ("9p: make rpc code common and rework flush code") +Closes: https://syzkaller.appspot.com/bug?extid=3ce7863f8fc836a427e7 +Cc: stable@vger.kernel.org +Signed-off-by: Vasiliy Kovalev +Message-ID: <20260415155237.182891-1-kovalev@altlinux.org> +Signed-off-by: Dominique Martinet +Signed-off-by: Greg Kroah-Hartman +--- + net/9p/client.c | 2 ++ + 1 file changed, 2 insertions(+) + +--- a/net/9p/client.c ++++ b/net/9p/client.c +@@ -600,6 +600,8 @@ again: + + if (err == -ERESTARTSYS && c->status == Connected && + type == P9_TFLUSH) { ++ if (fatal_signal_pending(current)) ++ goto recalc_sigpending; + sigpending = 1; + clear_thread_flag(TIF_SIGPENDING); + goto again; diff --git a/queue-7.1/ntfs-add-bounds-check-before-accessing-ea-entries.patch b/queue-7.1/ntfs-add-bounds-check-before-accessing-ea-entries.patch new file mode 100644 index 0000000000..30b06a7353 --- /dev/null +++ b/queue-7.1/ntfs-add-bounds-check-before-accessing-ea-entries.patch @@ -0,0 +1,57 @@ +From 937282f7d15b593d0be765fa2ced164130ec87f7 Mon Sep 17 00:00:00 2001 +From: Hyunchul Lee +Date: Sat, 23 May 2026 13:14:23 +0900 +Subject: ntfs: add bounds check before accessing EA entries + +From: Hyunchul Lee + +commit 937282f7d15b593d0be765fa2ced164130ec87f7 upstream. + +in ntfs_ea_lookup and ntfs_listxattr, this verifies that there is enough +space in the EA entry before accessing the next_entry_offset field of +the EA entry. + +Cc: stable@vger.kernel.org # v7.1 +Signed-off-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/ea.c | 16 ++++++++-------- + 1 file changed, 8 insertions(+), 8 deletions(-) + +--- a/fs/ntfs/ea.c ++++ b/fs/ntfs/ea.c +@@ -53,11 +53,11 @@ static int ntfs_ea_lookup(char *ea_buf, + loff_t offset, p_ea_size; + unsigned int next; + +- if (ea_buf_size < sizeof(struct ea_attr)) +- goto out; +- + offset = 0; + do { ++ if (ea_buf_size - offset < sizeof(struct ea_attr)) ++ break; ++ + p_ea = (const struct ea_attr *)&ea_buf[offset]; + next = le32_to_cpu(p_ea->next_entry_offset); + p_ea_size = next ? next : (ea_buf_size - offset); +@@ -479,13 +479,13 @@ ssize_t ntfs_listxattr(struct dentry *de + if (ea_info_qsize > ea_buf_size || ea_info_qsize == 0) + goto out; + +- if (ea_info_qsize < sizeof(struct ea_attr)) { +- err = -EIO; +- goto out; +- } +- + offset = 0; + do { ++ if (ea_info_qsize - offset < sizeof(struct ea_attr)) { ++ err = -EIO; ++ goto out; ++ } ++ + p_ea = (const struct ea_attr *)&ea_buf[offset]; + next = le32_to_cpu(p_ea->next_entry_offset); + ea_size = next ? next : (ea_info_qsize - offset); diff --git a/queue-7.1/ntfs-add-wq_percpu-to-alloc_workqueue-users.patch b/queue-7.1/ntfs-add-wq_percpu-to-alloc_workqueue-users.patch new file mode 100644 index 0000000000..9e40ffa832 --- /dev/null +++ b/queue-7.1/ntfs-add-wq_percpu-to-alloc_workqueue-users.patch @@ -0,0 +1,50 @@ +From 38e8db370843b518ff9bee4af46c6b800684cc78 Mon Sep 17 00:00:00 2001 +From: Marco Crivellari +Date: Thu, 14 May 2026 15:54:08 +0200 +Subject: ntfs: Add WQ_PERCPU to alloc_workqueue users +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +From: Marco Crivellari + +commit 38e8db370843b518ff9bee4af46c6b800684cc78 upstream. + +This continues the effort to refactor workqueue APIs, which began with +the introduction of new workqueues and a new alloc_workqueue flag in: + + commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") + commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") + +The refactoring is going to alter the default behavior of +alloc_workqueue() to be unbound by default. + +With the introduction of the WQ_PERCPU flag (equivalent to !WQ_UNBOUND), +any alloc_workqueue() caller that doesn’t explicitly specify WQ_UNBOUND +must now use WQ_PERCPU. For more details see the Link tag below. + +In order to keep alloc_workqueue() behavior identical, explicitly request +WQ_PERCPU. + +Cc: stable@vger.kernel.org # v7.1 +Link: https://lore.kernel.org/all/20250221112003.1dSuoGyc@linutronix.de/ +Suggested-by: Tejun Heo +Signed-off-by: Marco Crivellari +Reviewed-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/super.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/fs/ntfs/super.c ++++ b/fs/ntfs/super.c +@@ -2656,7 +2656,7 @@ MODULE_ALIAS_FS("ntfs"); + + static int ntfs_workqueue_init(void) + { +- ntfs_wq = alloc_workqueue("ntfs-bg-io", 0, 0); ++ ntfs_wq = alloc_workqueue("ntfs-bg-io", WQ_PERCPU, 0); + if (!ntfs_wq) + return -ENOMEM; + return 0; diff --git a/queue-7.1/ntfs-avoid-heap-allocation-for-free-cluster-readahead-state.patch b/queue-7.1/ntfs-avoid-heap-allocation-for-free-cluster-readahead-state.patch new file mode 100644 index 0000000000..14534b05cf --- /dev/null +++ b/queue-7.1/ntfs-avoid-heap-allocation-for-free-cluster-readahead-state.patch @@ -0,0 +1,109 @@ +From c05132077df57a384919f61d7f8a8e76d748a6d4 Mon Sep 17 00:00:00 2001 +From: DaeMyung Kang +Date: Fri, 22 May 2026 23:20:48 +0900 +Subject: ntfs: avoid heap allocation for free-cluster readahead state + +From: DaeMyung Kang + +commit c05132077df57a384919f61d7f8a8e76d748a6d4 upstream. + +get_nr_free_clusters() allocates a temporary file_ra_state before it +publishes the precomputed free cluster count, sets NVolFreeClusterKnown(), +and wakes vol->free_waitq. If that allocation fails, the worker returns +without setting the flag or waking waiters, so callers waiting for the free +count can block indefinitely. + +The readahead state is only used synchronously while scanning the bitmap. +Keep it on the stack and pass it by address to the readahead helper. This +eliminates the early allocation failure path instead of adding a special +case that publishes a conservative count and wakes the waitqueue. +Zero-initialize the on-stack state because file_ra_state_init() only sets +ra_pages and prev_pos. + +Apply the same treatment to __get_nr_free_mft_records(), which scans the +MFT bitmap with the same short-lived readahead state. + +Cc: stable@vger.kernel.org # v7.1 +Signed-off-by: DaeMyung Kang +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/super.c | 22 ++++++---------------- + 1 file changed, 6 insertions(+), 16 deletions(-) + +--- a/fs/ntfs/super.c ++++ b/fs/ntfs/super.c +@@ -1955,7 +1955,7 @@ s64 get_nr_free_clusters(struct ntfs_vol + struct address_space *mapping = vol->lcnbmp_ino->i_mapping; + struct folio *folio; + pgoff_t index, max_index; +- struct file_ra_state *ra; ++ struct file_ra_state ra = { 0 }; + + ntfs_debug("Entering."); + /* Serialize accesses to the cluster bitmap. */ +@@ -1963,11 +1963,7 @@ s64 get_nr_free_clusters(struct ntfs_vol + if (NVolFreeClusterKnown(vol)) + return atomic64_read(&vol->free_clusters); + +- ra = kzalloc(sizeof(*ra), GFP_NOFS); +- if (!ra) +- return 0; +- +- file_ra_state_init(ra, mapping); ++ file_ra_state_init(&ra, mapping); + + /* + * Convert the number of bits into bytes rounded up, then convert into +@@ -1986,7 +1982,7 @@ s64 get_nr_free_clusters(struct ntfs_vol + * Get folio from page cache, getting it from backing store + * if necessary, and increment the use count. + */ +- folio = ntfs_get_locked_folio(mapping, index, max_index, ra); ++ folio = ntfs_get_locked_folio(mapping, index, max_index, &ra); + + /* Ignore pages which errored synchronously. */ + if (IS_ERR(folio)) { +@@ -2025,7 +2021,6 @@ s64 get_nr_free_clusters(struct ntfs_vol + else + atomic64_set(&vol->free_clusters, nr_free); + +- kfree(ra); + NVolSetFreeClusterKnown(vol); + wake_up_all(&vol->free_waitq); + ntfs_debug("Exiting."); +@@ -2080,15 +2075,11 @@ static unsigned long __get_nr_free_mft_r + struct address_space *mapping = vol->mftbmp_ino->i_mapping; + struct folio *folio; + pgoff_t index; +- struct file_ra_state *ra; ++ struct file_ra_state ra = { 0 }; + + ntfs_debug("Entering."); + +- ra = kzalloc(sizeof(*ra), GFP_NOFS); +- if (!ra) +- return 0; +- +- file_ra_state_init(ra, mapping); ++ file_ra_state_init(&ra, mapping); + + /* Use multiples of 4 bytes, thus max_size is PAGE_SIZE / 4. */ + ntfs_debug("Reading $MFT/$BITMAP, max_index = 0x%lx, max_size = 0x%lx.", +@@ -2100,7 +2091,7 @@ static unsigned long __get_nr_free_mft_r + * Get folio from page cache, getting it from backing store + * if necessary, and increment the use count. + */ +- folio = ntfs_get_locked_folio(mapping, index, max_index, ra); ++ folio = ntfs_get_locked_folio(mapping, index, max_index, &ra); + + /* Ignore pages which errored synchronously. */ + if (IS_ERR(folio)) { +@@ -2132,7 +2123,6 @@ static unsigned long __get_nr_free_mft_r + else + atomic64_set(&vol->free_mft_records, nr_free); + +- kfree(ra); + ntfs_debug("Exiting."); + return nr_free; + } diff --git a/queue-7.1/ntfs-avoid-self-deadlock-during-inode-eviction.patch b/queue-7.1/ntfs-avoid-self-deadlock-during-inode-eviction.patch new file mode 100644 index 0000000000..1f5681bb4f --- /dev/null +++ b/queue-7.1/ntfs-avoid-self-deadlock-during-inode-eviction.patch @@ -0,0 +1,71 @@ +From 77dc384207d5fa63ba97c3bf3285fe1215a1cbf6 Mon Sep 17 00:00:00 2001 +From: Hyunchul Lee +Date: Thu, 2 Jul 2026 14:28:16 +0900 +Subject: ntfs: avoid self-deadlock during inode eviction + +From: Hyunchul Lee + +commit 77dc384207d5fa63ba97c3bf3285fe1215a1cbf6 upstream. + +An attribute-list update performed while allocating clusters can drop the +last reference to the temporary attribute inode. Evicting that inode +drops its reference to the base inode and can invoke ntfs_drop_big_inode() +for the base inode from within the base inode's own writeback path. + +If the base inode is unlinked, ntfs_drop_big_inode() calls +truncate_setsize(), which waits for the inode's folio writeback to +complete. The same writeback worker is responsible for completing that +writeback, so it waits for itself indefinitely. + +Prevent this self-deadlock by grabbing a reference to the base inode at the +beginning of ntfs_writepages() and releasing it at the end of the function. +This defers eviction until all bios have been submitted, allowing the wait +for folio writeback to complete safely. + +Fixes: b041ca562526 ("ntfs: update iomap and address space operations") +Cc: stable@vger.kernel.org +Signed-off-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/aops.c | 17 ++++++++++++++++- + 1 file changed, 16 insertions(+), 1 deletion(-) + +diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c +index 1fbf832ad165..88e5b2def66c 100644 +--- a/fs/ntfs/aops.c ++++ b/fs/ntfs/aops.c +@@ -251,6 +251,8 @@ static int ntfs_writepages(struct address_space *mapping, + .wbc = wbc, + .ops = &ntfs_writeback_ops, + }; ++ bool need_iput = false; ++ int ret; + + if (NVolShutdown(ni->vol)) + return -EIO; +@@ -267,7 +269,20 @@ static int ntfs_writepages(struct address_space *mapping, + return -EOPNOTSUPP; + } + +- return iomap_writepages(&wpc); ++ /* ++ * Prevent eviction in writeback to avoid deadlock in ++ * ntfs_drop_big_inode(). ++ */ ++ if ((ni->type == AT_DATA || ni->type == AT_INDEX_ALLOCATION) && ++ igrab(inode)) ++ need_iput = true; ++ ++ ret = iomap_writepages(&wpc); ++ ++ if (need_iput) ++ iput(inode); ++ ++ return ret; + } + + static int ntfs_swap_activate(struct swap_info_struct *sis, +-- +2.55.0 + diff --git a/queue-7.1/ntfs-centalize-index_root-header-validation.patch b/queue-7.1/ntfs-centalize-index_root-header-validation.patch new file mode 100644 index 0000000000..79af6f4eed --- /dev/null +++ b/queue-7.1/ntfs-centalize-index_root-header-validation.patch @@ -0,0 +1,104 @@ +From 8b97b302f553a480fb76d2afd53cd6c0635a9dcd Mon Sep 17 00:00:00 2001 +From: Hyunchul Lee +Date: Sat, 23 May 2026 13:14:21 +0900 +Subject: ntfs: centalize $INDEX_ROOT header validation + +From: Hyunchul Lee + +commit 8b97b302f553a480fb76d2afd53cd6c0635a9dcd upstream. + +Add a dedicated helper to perform stricter validation of $INDEX_ROOT and +use it for both directory inodes and named index inodes. This keeps the +root size and header geometry checks consistent across both read paths. + +Cc: stable@vger.kernel.org # v7.1 +Tested-by: woot000 +Signed-off-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/index.c | 18 ++++++++++++++++++ + fs/ntfs/index.h | 3 +++ + fs/ntfs/inode.c | 11 ++--------- + 3 files changed, 23 insertions(+), 9 deletions(-) + +--- a/fs/ntfs/index.c ++++ b/fs/ntfs/index.c +@@ -544,6 +544,24 @@ int ntfs_index_block_inconsistent(struct + return 0; + } + ++int ntfs_index_root_inconsistent(struct ntfs_volume *vol, ++ const struct attr_record *a, ++ const struct index_root *ir, u64 inum) ++{ ++ u32 value_length = le32_to_cpu(a->data.resident.value_length); ++ ++ if (value_length < offsetof(struct index_root, index)) { ++ ntfs_error(vol->sb, "$INDEX_ROOT in inode %llu is too small.", ++ (unsigned long long)inum); ++ return -EIO; ++ } ++ ++ return ntfs_index_header_inconsistent(vol, &ir->index, ++ value_length - ++ offsetof(struct index_root, index), ++ inum); ++} ++ + static struct index_root *ntfs_ir_lookup(struct ntfs_inode *ni, __le16 *name, + u32 name_len, struct ntfs_attr_search_ctx **ctx) + { +--- a/fs/ntfs/index.h ++++ b/fs/ntfs/index.h +@@ -89,6 +89,9 @@ struct ntfs_index_context { + bool sync_write; + }; + ++int ntfs_index_root_inconsistent(struct ntfs_volume *vol, ++ const struct attr_record *a, ++ const struct index_root *ir, u64 inum); + int ntfs_index_block_inconsistent(struct ntfs_volume *vol, + const struct index_block *ib, + u32 block_size, s64 vcn, u64 inum); +--- a/fs/ntfs/inode.c ++++ b/fs/ntfs/inode.c +@@ -896,7 +896,6 @@ skip_attr_list_load: + */ + if (S_ISDIR(vi->i_mode)) { + struct index_root *ir; +- u8 *ir_end, *index_end; + + view_index_meta: + /* It is a directory, find index root attribute. */ +@@ -946,10 +945,7 @@ view_index_meta: + } + ir = (struct index_root *)((u8 *)a + + le16_to_cpu(a->data.resident.value_offset)); +- ir_end = (u8 *)ir + le32_to_cpu(a->data.resident.value_length); +- index_end = (u8 *)&ir->index + +- le32_to_cpu(ir->index.index_length); +- if (index_end > ir_end) { ++ if (ntfs_index_root_inconsistent(ni->vol, a, ir, ni->mft_no)) { + ntfs_error(vi->i_sb, "Directory index is corrupt."); + goto unm_err_out; + } +@@ -1492,7 +1488,6 @@ static int ntfs_read_locked_index_inode( + struct attr_record *a; + struct ntfs_attr_search_ctx *ctx; + struct index_root *ir; +- u8 *ir_end, *index_end; + int err = 0; + + ntfs_debug("Entering for i_ino 0x%llx.", ni->mft_no); +@@ -1543,9 +1538,7 @@ static int ntfs_read_locked_index_inode( + } + + ir = (struct index_root *)((u8 *)a + le16_to_cpu(a->data.resident.value_offset)); +- ir_end = (u8 *)ir + le32_to_cpu(a->data.resident.value_length); +- index_end = (u8 *)&ir->index + le32_to_cpu(ir->index.index_length); +- if (index_end > ir_end) { ++ if (ntfs_index_root_inconsistent(vol, a, ir, ni->mft_no)) { + ntfs_error(vi->i_sb, "Index is corrupt."); + goto unm_err_out; + } diff --git a/queue-7.1/ntfs-detect-mapping-pairs-lcn-accumulator-overflow.patch b/queue-7.1/ntfs-detect-mapping-pairs-lcn-accumulator-overflow.patch new file mode 100644 index 0000000000..619e2620dd --- /dev/null +++ b/queue-7.1/ntfs-detect-mapping-pairs-lcn-accumulator-overflow.patch @@ -0,0 +1,51 @@ +From ec4f061f2219e0f0c6465d56d0380bf749235a53 Mon Sep 17 00:00:00 2001 +From: Samuel Moelius +Date: Wed, 3 Jun 2026 17:41:09 +0000 +Subject: ntfs: detect mapping-pairs LCN accumulator overflow + +From: Samuel Moelius + +commit ec4f061f2219e0f0c6465d56d0380bf749235a53 upstream. + +The NTFS mapping-pairs parser accumulates relative LCN deltas in a +signed integer. A corrupted attribute can drive that addition past +the representable range. + +One corrupt runlist shape sets the accumulated LCN to S64_MAX and +then adds a delta of 1 in the next mapping-pairs entry. + +Signed overflow is undefined and can turn an invalid runlist into a +different set of physical clusters. + +Check the LCN addition for overflow before storing the next run. + +Cc: stable@vger.kernel.org # v7.1 +Assisted-by: Codex:gpt-5.5-cyber-preview +Signed-off-by: Samuel Moelius +Reviewed-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/runlist.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/fs/ntfs/runlist.c b/fs/ntfs/runlist.c +index d8e8aa7b5bc0..cbb6576cf725 100644 +--- a/fs/ntfs/runlist.c ++++ b/fs/ntfs/runlist.c +@@ -860,7 +860,11 @@ struct runlist_element *ntfs_mapping_pairs_decompress(const struct ntfs_volume * + for (deltaxcn = (s8)buf[b--]; b > b2; b--) + deltaxcn = (deltaxcn << 8) + buf[b]; + /* Change the current lcn to its new value. */ +- lcn += deltaxcn; ++ if (unlikely(check_add_overflow(lcn, deltaxcn, &lcn))) { ++ ntfs_error(vol->sb, ++ "LCN overflow in mapping pairs array."); ++ goto err_out; ++ } + #ifdef DEBUG + /* + * On NTFS 1.2-, apparently can have lcn == -1 to +-- +2.55.0 + diff --git a/queue-7.1/ntfs-do-not-replace-volume-name-after-lookup-errors.patch b/queue-7.1/ntfs-do-not-replace-volume-name-after-lookup-errors.patch new file mode 100644 index 0000000000..dc266c1c19 --- /dev/null +++ b/queue-7.1/ntfs-do-not-replace-volume-name-after-lookup-errors.patch @@ -0,0 +1,51 @@ +From 40d88020d0797f96a93edd2e8edc413c2e2d8f84 Mon Sep 17 00:00:00 2001 +From: DaeMyung Kang +Date: Sat, 30 May 2026 23:35:10 +0900 +Subject: ntfs: do not replace volume name after lookup errors + +From: DaeMyung Kang + +commit 40d88020d0797f96a93edd2e8edc413c2e2d8f84 upstream. + +ntfs_write_volume_label() removes an existing $VOLUME_NAME attribute and +then adds the replacement. The old code only distinguished lookup success +from all other results, so any lookup error was treated like an absent +label and the add path still ran. + +That is unsafe once lookup-time validation rejects corrupt $VOLUME_NAME +records with -EIO: the corrupt record would remain in place and a second +$VOLUME_NAME record could be appended next to it. + +Only add the replacement after the old label was removed successfully or +after lookup returned -ENOENT. Propagate all other lookup errors, and +also stop if removing the old attribute fails. + +Cc: stable@vger.kernel.org # v7.1 +Signed-off-by: DaeMyung Kang +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/super.c | 11 ++++++++--- + 1 file changed, 8 insertions(+), 3 deletions(-) + +--- a/fs/ntfs/super.c ++++ b/fs/ntfs/super.c +@@ -452,10 +452,15 @@ int ntfs_write_volume_label(struct ntfs_ + goto out; + } + +- if (!ntfs_attr_lookup(AT_VOLUME_NAME, NULL, 0, 0, 0, NULL, 0, +- ctx)) +- ntfs_attr_record_rm(ctx); ++ ret = ntfs_attr_lookup(AT_VOLUME_NAME, NULL, 0, 0, 0, NULL, 0, ++ ctx); ++ if (!ret) ++ ret = ntfs_attr_record_rm(ctx); ++ else if (ret == -ENOENT) ++ ret = 0; + ntfs_attr_put_search_ctx(ctx); ++ if (ret) ++ goto out; + + ret = ntfs_resident_attr_record_add(vol_ni, AT_VOLUME_NAME, AT_UNNAMED, 0, + (u8 *)uname, uname_len * sizeof(__le16), 0); diff --git a/queue-7.1/ntfs-fail-attrlist-updates-when-the-superblock-is-inactive.patch b/queue-7.1/ntfs-fail-attrlist-updates-when-the-superblock-is-inactive.patch new file mode 100644 index 0000000000..0f8081c09a --- /dev/null +++ b/queue-7.1/ntfs-fail-attrlist-updates-when-the-superblock-is-inactive.patch @@ -0,0 +1,58 @@ +From 0ebe8f625ab0520217a425d7cd366e4670484941 Mon Sep 17 00:00:00 2001 +From: Peiyang He +Date: Mon, 6 Jul 2026 12:00:15 +0800 +Subject: ntfs: fail attrlist updates when the superblock is inactive + +From: Peiyang He + +commit 0ebe8f625ab0520217a425d7cd366e4670484941 upstream. + +generic_shutdown_super() clears SB_ACTIVE before evicting cached inodes. +If eviction selects the fake inode for a base inode's unnamed +$ATTRIBUTE_LIST attribute, ntfs_evict_big_inode() drops the fake inode's +reference on the base inode while the fake inode is still hashed and marked +I_FREEING. + +That iput can synchronously write back the base inode. The writeback path +may update mapping pairs and call ntfs_attrlist_update(), which +unconditionally calls ntfs_attr_iget() for the same $ATTRIBUTE_LIST fake +inode. VFS then finds the I_FREEING inode and waits for eviction to finish, +but the current task is still inside that eviction path, causing a +self-deadlock in find_inode(). + +Fix this by mirroring the teardown guard used by __ntfs_write_inode(): +once SB_ACTIVE has been cleared, do not try to iget the attribute-list +fake inode. Return -EIO so teardown aborts the update instead of waiting on +the inode it is evicting. + +Reported-by: Peiyang He +Closes: https://lore.kernel.org/all/AB8D5E603E6EA856+ae5f622a-dd3a-4e38-bdd2-42276ae0e1a8@smail.nju.edu.cn/ +Fixes: 495e90fa3348 ("ntfs: update attrib operations") +Cc: stable@vger.kernel.org +Signed-off-by: Peiyang He +Assisted-by: Codex:gpt-5.5 +Reviewed-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/attrlist.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +--- a/fs/ntfs/attrlist.c ++++ b/fs/ntfs/attrlist.c +@@ -57,6 +57,15 @@ int ntfs_attrlist_update(struct ntfs_ino + struct ntfs_inode *attr_ni; + int err; + ++ /* ++ * generic_shutdown_super() clears SB_ACTIVE before evicting cached ++ * inodes. Do not look up the attribute-list inode after SB_ACTIVE has ++ * been cleared; it may already be I_FREEING, and waiting on it can ++ * self-deadlock. ++ */ ++ if (!(VFS_I(base_ni)->i_sb->s_flags & SB_ACTIVE)) ++ return -EIO; ++ + attr_vi = ntfs_attr_iget(VFS_I(base_ni), AT_ATTRIBUTE_LIST, AT_UNNAMED, 0); + if (IS_ERR(attr_vi)) { + err = PTR_ERR(attr_vi); diff --git a/queue-7.1/ntfs-fix-hole-runlist-memory-leak-in-insert-range-error-path.patch b/queue-7.1/ntfs-fix-hole-runlist-memory-leak-in-insert-range-error-path.patch new file mode 100644 index 0000000000..167ff9e1de --- /dev/null +++ b/queue-7.1/ntfs-fix-hole-runlist-memory-leak-in-insert-range-error-path.patch @@ -0,0 +1,37 @@ +From 06769b8f23b4b645b270c438649fff79768fb6fe Mon Sep 17 00:00:00 2001 +From: Peiyang He +Date: Sun, 5 Jul 2026 19:14:09 +0800 +Subject: ntfs: fix hole runlist memory leak in insert range error path + +From: Peiyang He + +commit 06769b8f23b4b645b270c438649fff79768fb6fe upstream. + +ntfs_non_resident_attr_insert_range() allocates hole_rl before mapping the +whole runlist. If ntfs_attr_map_whole_runlist() fails, the error path drops +ni->runlist.lock and returns without freeing hole_rl. This leaks memory +of sizeof(*hole_rl) * 2 bytes. + +Fix this memory leak by freeing hole_rl before returning from +that error path, matching the later error paths in the same function. + +Fixes: 495e90fa3348 ("ntfs: update attrib operations") +Cc: stable@vger.kernel.org +Signed-off-by: Peiyang He +Reviewed-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/attrib.c | 1 + + 1 file changed, 1 insertion(+) + +--- a/fs/ntfs/attrib.c ++++ b/fs/ntfs/attrib.c +@@ -5327,6 +5327,7 @@ int ntfs_non_resident_attr_insert_range( + ret = ntfs_attr_map_whole_runlist(ni); + if (ret) { + up_write(&ni->runlist.lock); ++ kfree(hole_rl); + return ret; + } + diff --git a/queue-7.1/ntfs-fix-incorrect-size-of-symbolic-link.patch b/queue-7.1/ntfs-fix-incorrect-size-of-symbolic-link.patch new file mode 100644 index 0000000000..a78af29bf1 --- /dev/null +++ b/queue-7.1/ntfs-fix-incorrect-size-of-symbolic-link.patch @@ -0,0 +1,31 @@ +From 05a5ff86a7f12c861e3516d3dc4d092ce620742d Mon Sep 17 00:00:00 2001 +From: Hyunchul Lee +Date: Mon, 15 Jun 2026 08:49:52 +0900 +Subject: ntfs: fix incorrect size of symbolic link + +From: Hyunchul Lee + +commit 05a5ff86a7f12c861e3516d3dc4d092ce620742d upstream. + +This patch fixes the issue where a symbolic link size is displayed as 0. + +Cc: stable@vger.kernel.org # v7.1 +Signed-off-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/inode.c | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/fs/ntfs/inode.c ++++ b/fs/ntfs/inode.c +@@ -1201,6 +1201,9 @@ no_data_attr_special_case: + else + vi->i_blocks = ni->allocated_size >> 9; + ++ if (S_ISLNK(vi->i_mode) && ni->target) ++ vi->i_size = strlen(ni->target); ++ + ntfs_debug("Done."); + return 0; + unm_err_out: diff --git a/queue-7.1/ntfs-fix-mrec_lock-abba-deadlock-in-rename.patch b/queue-7.1/ntfs-fix-mrec_lock-abba-deadlock-in-rename.patch new file mode 100644 index 0000000000..f307c1b27e --- /dev/null +++ b/queue-7.1/ntfs-fix-mrec_lock-abba-deadlock-in-rename.patch @@ -0,0 +1,144 @@ +From eb94f5a41a193a425e09a63cb75dffd151d8f42e Mon Sep 17 00:00:00 2001 +From: Peiyang He +Date: Tue, 30 Jun 2026 11:08:56 +0800 +Subject: ntfs: fix mrec_lock ABBA deadlock in rename + +From: Peiyang He + +commit eb94f5a41a193a425e09a63cb75dffd151d8f42e upstream. + +ntfs_file_fsync(), ntfs_dir_fsync() and __ntfs_write_inode() lock an +inode's mrec_lock before taking the mrec_lock of its parent directory. + +ntfs_rename() takes old_ni->mrec_lock and old_dir_ni->mrec_lock +before taking new_ni->mrec_lock for an existing target, or +new_dir_ni->mrec_lock for a cross-directory rename. +This can deadlock when ntfs_file_fsync() or __ntfs_write_inode() holds +the target inode, or when ntfs_dir_fsync() holds a child target +directory, while rename() holds the parent directory and waits for the +target. + +Fix this by locking the existing target inode before taking any parent +directory mrec_lock. For cross-directory renames where the target parent +is a descendant of the source parent, lock the target parent before the +source parent so the directory order matches the child-to-parent order used +by ntfs_file_fsync(), ntfs_dir_fsync(), and __ntfs_write_inode(). + +Reported-by: Peiyang He +Closes: https://lore.kernel.org/all/C4D296F0E9F3D66C+9397ffbc-eb55-44bb-9b3f-5da4809e7955@smail.nju.edu.cn/ +Fixes: af0db57d4293 ("ntfs: update inode operations") +Cc: stable@vger.kernel.org +Signed-off-by: Peiyang He +Assisted-by: Codex:gpt-5.5 +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/namei.c | 62 ++++++++++++++++++++++++++++---------------------------- + 1 file changed, 31 insertions(+), 31 deletions(-) + +--- a/fs/ntfs/namei.c ++++ b/fs/ntfs/namei.c +@@ -1264,6 +1264,7 @@ static int ntfs_rename(struct mnt_idmap + struct ntfs_volume *vol = NTFS_SB(sb); + struct ntfs_inode *old_ni, *new_ni = NULL; + struct ntfs_inode *old_dir_ni = NTFS_I(old_dir), *new_dir_ni = NTFS_I(new_dir); ++ bool new_dir_first = false; + + if (NVolShutdown(old_dir_ni->vol)) + return -EIO; +@@ -1299,36 +1300,39 @@ static int ntfs_rename(struct mnt_idmap + old_inode = old_dentry->d_inode; + new_inode = new_dentry->d_inode; + old_ni = NTFS_I(old_inode); ++ if (new_inode) ++ new_ni = NTFS_I(new_inode); ++ if (old_dir != new_dir) ++ new_dir_first = is_subdir(new_dentry->d_parent, ++ old_dentry->d_parent); + + if (!(vol->vol_flags & VOLUME_IS_DIRTY)) + ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY); + + mutex_lock_nested(&old_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL); +- mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); ++ if (new_ni) ++ mutex_lock_nested(&new_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_2); ++ ++ if (old_dir == new_dir) { ++ mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); ++ } else if (new_dir_first) { ++ mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); ++ mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2); ++ } else { ++ mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT); ++ mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2); ++ } + +- if (NInoBeingDeleted(old_ni) || NInoBeingDeleted(old_dir_ni)) { ++ if (NInoBeingDeleted(old_ni) || NInoBeingDeleted(old_dir_ni) || ++ (new_ni && NInoBeingDeleted(new_ni)) || ++ (old_dir != new_dir && NInoBeingDeleted(new_dir_ni))) { + err = -ENOENT; +- goto unlock_old; ++ goto err_out; + } + + is_dir = S_ISDIR(old_inode->i_mode); + + if (new_inode) { +- new_ni = NTFS_I(new_inode); +- mutex_lock_nested(&new_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_2); +- if (old_dir != new_dir) { +- mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2); +- if (NInoBeingDeleted(new_dir_ni)) { +- err = -ENOENT; +- goto err_out; +- } +- } +- +- if (NInoBeingDeleted(new_ni)) { +- err = -ENOENT; +- goto err_out; +- } +- + if (is_dir) { + struct mft_record *ni_mrec; + +@@ -1346,14 +1350,6 @@ static int ntfs_rename(struct mnt_idmap + err = ntfs_delete(new_ni, new_dir_ni, uname_new, new_name_len, false); + if (err) + goto err_out; +- } else { +- if (old_dir != new_dir) { +- mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2); +- if (NInoBeingDeleted(new_dir_ni)) { +- err = -ENOENT; +- goto err_out; +- } +- } + } + + err = __ntfs_link(old_ni, new_dir_ni, uname_new, new_name_len); +@@ -1384,13 +1380,17 @@ static int ntfs_rename(struct mnt_idmap + inode_inc_iversion(new_dir); + + err_out: +- if (old_dir != new_dir) ++ if (old_dir == new_dir) { ++ mutex_unlock(&old_dir_ni->mrec_lock); ++ } else if (new_dir_first) { ++ mutex_unlock(&old_dir_ni->mrec_lock); + mutex_unlock(&new_dir_ni->mrec_lock); +- if (new_inode) ++ } else { ++ mutex_unlock(&new_dir_ni->mrec_lock); ++ mutex_unlock(&old_dir_ni->mrec_lock); ++ } ++ if (new_ni) + mutex_unlock(&new_ni->mrec_lock); +- +-unlock_old: +- mutex_unlock(&old_dir_ni->mrec_lock); + mutex_unlock(&old_ni->mrec_lock); + if (uname_new) + kmem_cache_free(ntfs_name_cache, uname_new); diff --git a/queue-7.1/ntfs-fix-off-by-one-in-mapping-pairs-decoding-bounds-checks.patch b/queue-7.1/ntfs-fix-off-by-one-in-mapping-pairs-decoding-bounds-checks.patch new file mode 100644 index 0000000000..ba8de30943 --- /dev/null +++ b/queue-7.1/ntfs-fix-off-by-one-in-mapping-pairs-decoding-bounds-checks.patch @@ -0,0 +1,70 @@ +From 18760a74ef7c28df93726445b5595162e62ed341 Mon Sep 17 00:00:00 2001 +From: Ron de Bruijn +Date: Sat, 30 May 2026 09:19:18 +0900 +Subject: ntfs: fix off-by-one in mapping pairs decoding bounds checks + +From: Ron de Bruijn + +commit 18760a74ef7c28df93726445b5595162e62ed341 upstream. + +In ntfs_mapping_pairs_decompress(), attr_end points one byte past the +end of the attribute record: + + attr_end = (u8 *)attr + le32_to_cpu(attr->length); + +The two bounds checks validating that mapping pair data bytes fit within +the attribute use strict greater-than (>), which allows a one-byte +out-of-bounds read when the data extends exactly to attr_end: + + b = *buf & 0xf; + if (b) { + if (unlikely(buf + b > attr_end)) // off-by-one + goto io_error; + for (deltaxcn = (s8)buf[b--]; b; b--) + deltaxcn = (deltaxcn << 8) + buf[b]; + } + +When buf + b == attr_end, the check evaluates to false and buf[b] reads +one byte past the valid attribute boundary. The same pattern appears in +the LCN delta bytes check. + +Fix both checks to use >= so that buf[b] at exactly attr_end is +correctly rejected as out of bounds. + +Cc: stable@vger.kernel.org # v7.1 +Signed-off-by: Ron de Bruijn +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/runlist.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +--- a/fs/ntfs/runlist.c ++++ b/fs/ntfs/runlist.c +@@ -763,7 +763,7 @@ struct runlist_element *ntfs_mapping_pai + buf = (u8 *)attr + + le16_to_cpu(attr->data.non_resident.mapping_pairs_offset); + attr_end = (u8 *)attr + le32_to_cpu(attr->length); +- if (unlikely(buf < (u8 *)attr || buf > attr_end)) { ++ if (unlikely(buf < (u8 *)attr || buf >= attr_end)) { + ntfs_error(vol->sb, "Corrupt attribute."); + return ERR_PTR(-EIO); + } +@@ -811,7 +811,7 @@ struct runlist_element *ntfs_mapping_pai + */ + b = *buf & 0xf; + if (b) { +- if (unlikely(buf + b > attr_end)) ++ if (unlikely(buf + b >= attr_end)) + goto io_error; + for (deltaxcn = (s8)buf[b--]; b; b--) + deltaxcn = (deltaxcn << 8) + buf[b]; +@@ -855,7 +855,7 @@ struct runlist_element *ntfs_mapping_pai + u8 b2 = *buf & 0xf; + + b = b2 + ((*buf >> 4) & 0xf); +- if (buf + b > attr_end) ++ if (buf + b >= attr_end) + goto io_error; + for (deltaxcn = (s8)buf[b--]; b > b2; b--) + deltaxcn = (deltaxcn << 8) + buf[b]; diff --git a/queue-7.1/ntfs-fix-warn_on-for-resident-attribute-in-ntfs_map_runlist_nolock.patch b/queue-7.1/ntfs-fix-warn_on-for-resident-attribute-in-ntfs_map_runlist_nolock.patch new file mode 100644 index 0000000000..e7343ec6d1 --- /dev/null +++ b/queue-7.1/ntfs-fix-warn_on-for-resident-attribute-in-ntfs_map_runlist_nolock.patch @@ -0,0 +1,63 @@ +From b8d6c528e9d57d263fee1a648409f84a68b2561d Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Thu, 2 Jul 2026 10:31:44 +0900 +Subject: ntfs: fix WARN_ON for resident attribute in ntfs_map_runlist_nolock() + +From: Namjae Jeon + +commit b8d6c528e9d57d263fee1a648409f84a68b2561d upstream. + +When ntfs_map_runlist_nolock() needs to look up the attribute extent +containing a target VCN (ctx_needs_reset == true), it calls +ntfs_attr_lookup() and then expects the result to be a non-resident +attribute, since only non-resident attributes have a mapping pairs +array to decompress. + +A crafted NTFS image can place a resident attribute where a non-resident +one is expected, causing ntfs_attr_lookup() to succeed but return a +resident attribute record. Previously this was caught only by a +WARN_ON(), which does not stop execution. The code then falls through to +read a->data.non_resident.highest_vcn from what is actually a resident +attribute, accessing the wrong union member and corrupting the VCN range +check. + +The caller path triggering this warning during mount is: + + ntfs_map_runlist_nolock + ntfs_empty_logfile + load_system_files + ntfs_fill_super + +In this path ctx is NULL, so ntfs_map_runlist_nolock() allocates a +temporary search context internally and sets ctx_needs_reset = true. +The existing resident-attribute guard in the ctx != NULL branch already +returns -EIO silently for the same condition; make the ctx_needs_reset +path consistent by replacing the WARN_ON() with the same -EIO error +return. + +This causes the crafted image to be rejected with a mount error instead +of triggering a kernel warning. + +Fixes: 495e90fa3348 ("ntfs: update attrib operations") +Cc: stable@vger.kernel.org +Reported-by: Sangho Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/attrib.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +--- a/fs/ntfs/attrib.c ++++ b/fs/ntfs/attrib.c +@@ -174,7 +174,10 @@ int ntfs_map_runlist_nolock(struct ntfs_ + err = -EIO; + goto err_out; + } +- WARN_ON(!ctx->attr->non_resident); ++ if (unlikely(!ctx->attr->non_resident)) { ++ err = -EIO; ++ goto err_out; ++ } + } + a = ctx->attr; + /* diff --git a/queue-7.1/ntfs-free-volume-wide-resources-on-fill_super-failure.patch b/queue-7.1/ntfs-free-volume-wide-resources-on-fill_super-failure.patch new file mode 100644 index 0000000000..df36cf169f --- /dev/null +++ b/queue-7.1/ntfs-free-volume-wide-resources-on-fill_super-failure.patch @@ -0,0 +1,73 @@ +From a9523a7d3b24b3a6b25ec1eb668ee6618cacf05e Mon Sep 17 00:00:00 2001 +From: DaeMyung Kang +Date: Thu, 21 May 2026 19:17:49 +0900 +Subject: ntfs: free volume-wide resources on fill_super failure + +From: DaeMyung Kang + +commit a9523a7d3b24b3a6b25ec1eb668ee6618cacf05e upstream. + +ntfs_fill_super()'s err_out_now path frees only the volume struct via +kfree(vol), leaving several vol-owned allocations behind on every mount +failure: + + - vol->nls_map, loaded by ntfs_init_fs_context() via + load_nls_default() (or replaced by an explicit nls= option in + ntfs_parse_param()), is never unload_nls()'d. + + - vol->volume_label, allocated by load_system_files() through + ntfs_ucstonls() once the $Volume name attribute has been parsed, is + not released by load_system_files()'s own error labels nor by the + fill_super() inline cleanup that only runs on d_make_root() + failure. Any later failure inside load_system_files() leaks it. + + - vol->lcn_empty_bits_per_page was kvfree()'d in + unl_upcase_iput_tmp_ino_err_out_now without clearing the pointer, + so it could not be folded into a single common cleanup. + +Because the failure paths never call ntfs_volume_free() and never reach +the d_make_root() inline cleanup block (it sits above the label and is +jumped over by the load_system_files() / kvmalloc failure gotos), these +resources accumulate per failed mount attempt with no chance of +recovery short of unloading the module. This is a silent leak: the +inodes loaded prior to failure remain hashed but generic_shutdown_super() +skips evict_inodes() when sb->s_root is unset, so no CHECK_DATA_CORRUPTION +warning is emitted either. + +Move the per-volume frees down to err_out_now and drop the +lcn_empty_bits_per_page kvfree() from the upper label so the cleanup is +performed exactly once on every failure path. Using unconditional +kvfree() / kfree() / unload_nls() is safe because they all accept NULL +and the upper labels that previously freed nls_map (the d_make_root() +inline cleanup) already clear the pointer. + +Cc: stable@vger.kernel.org # v7.1 +Signed-off-by: DaeMyung Kang +Reviewed-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/super.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +--- a/fs/ntfs/super.c ++++ b/fs/ntfs/super.c +@@ -2536,8 +2536,6 @@ static int ntfs_fill_super(struct super_ + } + /* Error exit code path. */ + unl_upcase_iput_tmp_ino_err_out_now: +- if (vol->lcn_empty_bits_per_page) +- kvfree(vol->lcn_empty_bits_per_page); + /* + * Decrease the number of upcase users and destroy the global default + * upcase table if necessary. +@@ -2557,6 +2555,9 @@ iput_tmp_ino_err_out_now: + /* Errors at this stage are irrelevant. */ + err_out_now: + sb->s_fs_info = NULL; ++ kvfree(vol->lcn_empty_bits_per_page); ++ kfree(vol->volume_label); ++ unload_nls(vol->nls_map); + kfree(vol); + ntfs_debug("Failed, returning -EINVAL."); + lockdep_on(); diff --git a/queue-7.1/ntfs-grow-index-root-value-before-reparent-header-update.patch b/queue-7.1/ntfs-grow-index-root-value-before-reparent-header-update.patch new file mode 100644 index 0000000000..164e821ab0 --- /dev/null +++ b/queue-7.1/ntfs-grow-index-root-value-before-reparent-header-update.patch @@ -0,0 +1,156 @@ +From 0bb508fb3b97e4802ec727fd2af4d608f65dd190 Mon Sep 17 00:00:00 2001 +From: DaeMyung Kang +Date: Tue, 9 Jun 2026 00:49:15 +0900 +Subject: ntfs: grow index root value before reparent header update + +From: DaeMyung Kang + +commit 0bb508fb3b97e4802ec727fd2af4d608f65dd190 upstream. + +ntfs_ir_reparent() moves the resident index root entries into an index +block and leaves a small root stub containing the child VCN. That root +stub can be larger than the existing resident value. For example, an +empty root with value_length 48 has an index area of 32 bytes, while the +large-index root stub needs index_length and allocated_size of 40 bytes. + +The current code publishes the larger index.index_length and +index.allocated_size before resizing the resident value. If the resize +returns -ENOSPC, the recovery path can call ntfs_inode_add_attrlist(), +which looks attributes up again while the root header says +allocated_size 40 but the resident value still only provides 32 bytes of +index area. Lookup-time $INDEX_ROOT validation then correctly rejects +that transient layout as corrupt. + +This reproduces as a generic/013 failure under qemu. In the failing run, +the transient root had value_len=48, index_size=32, index_length=40, and +allocated_size=40, and ntfsprogs-plus ntfsck reported "Corrupt index +root in MFT record 1177". + +When the root stub grows, resize the resident value before publishing the +larger root header. If the resize fails, the old root remains valid for +recovery lookups. Keep the existing header-before-resize ordering for +shrink or same-size cases so the resident value never temporarily +exposes an allocated_size beyond its bounds. + +Cc: stable@vger.kernel.org # v7.1 +Signed-off-by: DaeMyung Kang +Reviewed-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/index.c | 78 ++++++++++++++++++++++++++++++++++---------------------- + 1 file changed, 48 insertions(+), 30 deletions(-) + +--- a/fs/ntfs/index.c ++++ b/fs/ntfs/index.c +@@ -1176,6 +1176,8 @@ static int ntfs_ir_reparent(struct ntfs_ + struct index_entry *ie; + struct index_block *ib = NULL; + s64 new_ib_vcn; ++ u32 index_length; ++ u32 old_value_length; + int ix_root_size; + int ret = 0; + +@@ -1223,6 +1225,21 @@ retry: + goto clear_bmp; + } + ++ old_value_length = le32_to_cpu(ctx->attr->data.resident.value_length); ++ index_length = le32_to_cpu(ir->index.entries_offset) + ++ sizeof(struct index_entry_header) + sizeof(s64); ++ ix_root_size = offsetof(struct index_root, index) + index_length; ++ /* Grow the resident value before publishing the larger root header. */ ++ if (ix_root_size > old_value_length) { ++ ret = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, ix_root_size); ++ if (ret) ++ goto resize_failed; ++ ++ icx->idx_ni->data_size = ix_root_size; ++ icx->idx_ni->initialized_size = ix_root_size; ++ icx->idx_ni->allocated_size = (ix_root_size + 7) & ~7; ++ } ++ + ntfs_ir_nill(ir); + + ie = ntfs_ie_get_first(&ir->index); +@@ -1231,48 +1248,49 @@ retry: + + ir->index.flags = LARGE_INDEX; + NInoSetIndexAllocPresent(icx->idx_ni); +- ir->index.index_length = cpu_to_le32(le32_to_cpu(ir->index.entries_offset) + +- le16_to_cpu(ie->length)); ++ ir->index.index_length = cpu_to_le32(index_length); + ir->index.allocated_size = ir->index.index_length; + +- ix_root_size = sizeof(struct index_root) - sizeof(struct index_header) + +- le32_to_cpu(ir->index.allocated_size); +- ret = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, ix_root_size); +- if (ret) { +- /* +- * When there is no space to build a non-resident +- * index, we may have to move the root to an extent +- */ +- if ((ret == -ENOSPC) && (ctx->al_entry || !ntfs_inode_add_attrlist(icx->idx_ni))) { ++ if (ix_root_size <= old_value_length) { ++ ret = ntfs_resident_attr_value_resize(ctx->mrec, ctx->attr, ix_root_size); ++ if (ret) ++ goto resize_failed; ++ ++ icx->idx_ni->data_size = ix_root_size; ++ icx->idx_ni->initialized_size = ix_root_size; ++ icx->idx_ni->allocated_size = (ix_root_size + 7) & ~7; ++ } ++ ntfs_ie_set_vcn(ie, new_ib_vcn); ++ goto err_out; ++ ++resize_failed: ++ /* ++ * When there is no space to build a non-resident ++ * index, we may have to move the root to an extent ++ */ ++ if ((ret == -ENOSPC) && (ctx->al_entry || !ntfs_inode_add_attrlist(icx->idx_ni))) { ++ ntfs_attr_put_search_ctx(ctx); ++ ctx = NULL; ++ ir = ntfs_ir_lookup(icx->idx_ni, icx->name, icx->name_len, &ctx); ++ if (ir && !ntfs_attr_record_move_away(ctx, ix_root_size - ++ le32_to_cpu(ctx->attr->data.resident.value_length))) { ++ if (ntfs_attrlist_update(ctx->base_ntfs_ino ? ++ ctx->base_ntfs_ino : ctx->ntfs_ino)) ++ goto clear_bmp; + ntfs_attr_put_search_ctx(ctx); + ctx = NULL; +- ir = ntfs_ir_lookup(icx->idx_ni, icx->name, icx->name_len, &ctx); +- if (ir && !ntfs_attr_record_move_away(ctx, ix_root_size - +- le32_to_cpu(ctx->attr->data.resident.value_length))) { +- if (ntfs_attrlist_update(ctx->base_ntfs_ino ? +- ctx->base_ntfs_ino : ctx->ntfs_ino)) +- goto clear_bmp; +- ntfs_attr_put_search_ctx(ctx); +- ctx = NULL; +- goto retry; +- } ++ goto retry; + } +- goto clear_bmp; +- } else { +- icx->idx_ni->data_size = icx->idx_ni->initialized_size = ix_root_size; +- icx->idx_ni->allocated_size = (ix_root_size + 7) & ~7; + } +- ntfs_ie_set_vcn(ie, new_ib_vcn); +- ++clear_bmp: ++ ntfs_ibm_clear(icx, new_ib_vcn); ++ goto err_out; + err_out: + kvfree(ib); + if (ctx) + ntfs_attr_put_search_ctx(ctx); + out: + return ret; +-clear_bmp: +- ntfs_ibm_clear(icx, new_ib_vcn); +- goto err_out; + } + + /* diff --git a/queue-7.1/ntfs-make-system-files-immutable-to-prevent-corruption.patch b/queue-7.1/ntfs-make-system-files-immutable-to-prevent-corruption.patch new file mode 100644 index 0000000000..106104fe29 --- /dev/null +++ b/queue-7.1/ntfs-make-system-files-immutable-to-prevent-corruption.patch @@ -0,0 +1,47 @@ +From f72df3a4c33b64de3418ec74d1ad4f028e09d161 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Thu, 2 Jul 2026 20:36:59 +0900 +Subject: ntfs: make system files immutable to prevent corruption + +From: Namjae Jeon + +commit f72df3a4c33b64de3418ec74d1ad4f028e09d161 upstream. + +When a system file such as $Bitmap is exposed via show_sys_files and +written from userspace, the volume is corrupted and, because the cluster +allocator scans $Bitmap through the same inode's page cache, a write to +$Bitmap also deadlocks writeback against the folio it already holds locked. + +These files are maintained by the driver itself and have no valid reason +to be written through the file interface. Mark base metadata files +(mft_no < FILE_first_user) as immutable during inode read so the VFS +rejects write, mmap, truncate and unlink with -EPERM. Directories are +skipped so the root and $Extend remain usable. Internal metadata updates +do not go through the VFS write path and are unaffected. + +Fixes: af0db57d4293 ("ntfs: update inode operations") +Cc: stable@vger.kernel.org +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/inode.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +--- a/fs/ntfs/inode.c ++++ b/fs/ntfs/inode.c +@@ -1184,6 +1184,15 @@ no_data_attr_special_case: + vi->i_flags |= S_IMMUTABLE; + + /* ++ * System files such as $Bitmap and $MFT are maintained by the driver ++ * itself, and writing them from userspace corrupts the volume. ++ * Always make them immutable regardless of the sys_immutable option. ++ * Directories are skipped so the root and $Extend stay usable. ++ */ ++ if (ni->mft_no < FILE_first_user && S_ISREG(vi->i_mode)) ++ vi->i_flags |= S_IMMUTABLE; ++ ++ /* + * The number of 512-byte blocks used on disk (for stat). This is in so + * far inaccurate as it doesn't account for any named streams or other + * special non-resident attributes, but that is how Windows works, too, diff --git a/queue-7.1/ntfs-not-change-0-byte-data-attribute-to-non-resident.patch b/queue-7.1/ntfs-not-change-0-byte-data-attribute-to-non-resident.patch new file mode 100644 index 0000000000..4749c45fc3 --- /dev/null +++ b/queue-7.1/ntfs-not-change-0-byte-data-attribute-to-non-resident.patch @@ -0,0 +1,58 @@ +From 0aad21570197973af4a1b25b3fb8ed3aeb9e7670 Mon Sep 17 00:00:00 2001 +From: Hyunchul Lee +Date: Thu, 28 May 2026 11:15:35 +0900 +Subject: ntfs: not change 0-byte $DATA attribute to non-resident + +From: Hyunchul Lee + +commit 0aad21570197973af4a1b25b3fb8ed3aeb9e7670 upstream. + +When ntfs_resident_attr_resize() cannot grow a resident attribute in +place, it retries after converting other resident attributes to +non-resident to free space in the MFT recrord. + +Do not select zero-length resident $DATA attributes for this conversion. +fsck treats 0-byte non-resident $DATA attribute as corruptions. + +Cc: stable@vger.kernel.org # v7.1 +Signed-off-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/attrib.c | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +--- a/fs/ntfs/attrib.c ++++ b/fs/ntfs/attrib.c +@@ -4621,10 +4621,12 @@ attr_resize_again: + while (!(err = ntfs_attr_lookup(AT_UNUSED, NULL, 0, 0, 0, NULL, 0, ctx))) { + struct inode *tvi; + struct attr_record *a; ++ u32 value_len; + + a = ctx->attr; + if (a->non_resident || a->type == AT_ATTRIBUTE_LIST) + continue; ++ value_len = le32_to_cpu(a->data.resident.value_length); + + if (ntfs_attr_can_be_non_resident(vol, a->type)) + continue; +@@ -4636,6 +4638,8 @@ attr_resize_again: + if (le32_to_cpu(a->length) <= (sizeof(struct attr_record) - sizeof(s64)) + + ((a->name_length * sizeof(__le16) + 7) & ~7) + 8) + continue; ++ if (a->type == AT_DATA && !value_len) ++ continue; + + if (a->type == AT_DATA) + tvi = ntfs_iget(sb, base_ni->mft_no); +@@ -4648,8 +4652,7 @@ attr_resize_again: + continue; + } + +- if (ntfs_attr_make_non_resident(NTFS_I(tvi), +- le32_to_cpu(ctx->attr->data.resident.value_length))) { ++ if (ntfs_attr_make_non_resident(NTFS_I(tvi), value_len)) { + iput(tvi); + continue; + } diff --git a/queue-7.1/ntfs-only-alias-volume-upcase-to-default-on-exact-match.patch b/queue-7.1/ntfs-only-alias-volume-upcase-to-default-on-exact-match.patch new file mode 100644 index 0000000000..d39e0cc865 --- /dev/null +++ b/queue-7.1/ntfs-only-alias-volume-upcase-to-default-on-exact-match.patch @@ -0,0 +1,64 @@ +From 8f4b6e8bda121ae3d4d8e332b789664604d3e9d2 Mon Sep 17 00:00:00 2001 +From: DaeMyung Kang +Date: Thu, 21 May 2026 19:17:51 +0900 +Subject: ntfs: only alias volume $UpCase to default on exact match + +From: DaeMyung Kang + +commit 8f4b6e8bda121ae3d4d8e332b789664604d3e9d2 upstream. + +load_and_init_upcase() currently aliases vol->upcase to the global +default upcase whenever the shared prefix matches, and then truncates +vol->upcase_len to that shorter prefix. The result is correct only by +accident: upcase[] accesses in name collation are gated by upcase_len, +so the prefix-equality alias produces the same fold output as keeping +the volume's own shorter table. + +Still, prefix equality is not equality: the volume table is logically +distinct from the default and should not be replaced by it unless they +are byte-for-byte identical. Use memcmp() to compare the complete table +in one expression and drop the now-redundant upcase_len rewrite. + +No user-visible change is expected for compliant volumes whose $UpCase +has exactly default_upcase_len entries; shorter volume tables are no +longer aliased to the default. + +Cc: stable@vger.kernel.org # v7.1 +Signed-off-by: DaeMyung Kang +Reviewed-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/super.c | 12 +++--------- + 1 file changed, 3 insertions(+), 9 deletions(-) + +--- a/fs/ntfs/super.c ++++ b/fs/ntfs/super.c +@@ -1329,7 +1329,6 @@ static bool load_and_init_upcase(struct + u8 *addr; + pgoff_t index, max_index; + unsigned int size; +- int i, max; + + ntfs_debug("Entering."); + /* Read upcase table and setup vol->upcase and vol->upcase_len. */ +@@ -1380,16 +1379,11 @@ read_partial_upcase_page: + mutex_unlock(&ntfs_lock); + return true; + } +- max = default_upcase_len; +- if (max > vol->upcase_len) +- max = vol->upcase_len; +- for (i = 0; i < max; i++) +- if (vol->upcase[i] != default_upcase[i]) +- break; +- if (i == max) { ++ if (default_upcase_len == vol->upcase_len && ++ !memcmp(vol->upcase, default_upcase, ++ default_upcase_len * sizeof(*default_upcase))) { + kvfree(vol->upcase); + vol->upcase = default_upcase; +- vol->upcase_len = max; + ntfs_nr_upcase_users++; + mutex_unlock(&ntfs_lock); + ntfs_debug("Volume specified $UpCase matches default. Using default."); diff --git a/queue-7.1/ntfs-reinit-search-context-before-volume-information-lookup.patch b/queue-7.1/ntfs-reinit-search-context-before-volume-information-lookup.patch new file mode 100644 index 0000000000..72ac1517ae --- /dev/null +++ b/queue-7.1/ntfs-reinit-search-context-before-volume-information-lookup.patch @@ -0,0 +1,40 @@ +From 45dd046ced0f5982a6d64ca449de3a61f5f15669 Mon Sep 17 00:00:00 2001 +From: DaeMyung Kang +Date: Sat, 30 May 2026 23:35:11 +0900 +Subject: ntfs: reinit search context before volume information lookup + +From: DaeMyung Kang + +commit 45dd046ced0f5982a6d64ca449de3a61f5f15669 upstream. + +On mount the volume inode is searched for $VOLUME_NAME and then, reusing +the same search context, for $VOLUME_INFORMATION. The $VOLUME_NAME lookup +is optional and its result is otherwise ignored. + +Once lookup-time validation can reject a corrupt $VOLUME_NAME with -EIO, +the search context is left in an undefined state: ntfs_attr_find() +documents that on an actual error @ctx->attr is undefined. Continuing the +$VOLUME_INFORMATION search from that context is not contractually valid. + +Reinitialize the search context before the $VOLUME_INFORMATION lookup so +it always starts from a well-defined state regardless of the +$VOLUME_NAME lookup outcome. + +Cc: stable@vger.kernel.org # v7.1 +Signed-off-by: DaeMyung Kang +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/super.c | 1 + + 1 file changed, 1 insertion(+) + +--- a/fs/ntfs/super.c ++++ b/fs/ntfs/super.c +@@ -1537,6 +1537,7 @@ iput_volume_failed: + vol->volume_label = NULL; + } + ++ ntfs_attr_reinit_search_ctx(ctx); + if (ntfs_attr_lookup(AT_VOLUME_INFORMATION, NULL, 0, 0, 0, NULL, 0, + ctx) || ctx->attr->non_resident || ctx->attr->flags) { + ntfs_attr_put_search_ctx(ctx); diff --git a/queue-7.1/ntfs-reject-non-resident-records-for-resident-only-attributes.patch b/queue-7.1/ntfs-reject-non-resident-records-for-resident-only-attributes.patch new file mode 100644 index 0000000000..5ed44bc2ed --- /dev/null +++ b/queue-7.1/ntfs-reject-non-resident-records-for-resident-only-attributes.patch @@ -0,0 +1,76 @@ +From 097cdfd0a55df5af82c9753833f39a8bfadbcfcb Mon Sep 17 00:00:00 2001 +From: DaeMyung Kang +Date: Tue, 9 Jun 2026 00:49:14 +0900 +Subject: ntfs: reject non-resident records for resident-only attributes + +From: DaeMyung Kang + +commit 097cdfd0a55df5af82c9753833f39a8bfadbcfcb upstream. + +The shared lookup-time attribute validator rejects non-resident +$FILE_NAME and $VOLUME_NAME records because their formats require +resident values and callers handle returned records as resident +attributes. Other resident-only attribute types still pass through the +generic non-resident mapping-pairs checks. + +That leaves real resident/non-resident union confusion paths. Inode load +looks up $STANDARD_INFORMATION and then reads data.resident.value_offset +without checking a->non_resident. ntfs_inode_sync_standard_information() +does the same when updating the standard information value. +ntfs_write_volume_flags() also looks up $VOLUME_INFORMATION and reads +data.resident.value_offset directly. $INDEX_ROOT callers in dir.c and +index.c depend on the same lookup contract before consuming the resident +index root value. + +Reject non-resident records for all resident-only attribute types in the +shared validator. Keep the existing $FILE_NAME and $VOLUME_NAME behavior, +but factor it through a helper and extend it to +$STANDARD_INFORMATION, $OBJECT_ID, $VOLUME_INFORMATION, $INDEX_ROOT, and +$EA_INFORMATION. For $OBJECT_ID and $EA_INFORMATION this is contract +hardening for resident-only formats; this patch only rejects the +non-resident form and does not add new resident value validation for +those types. + +Cc: stable@vger.kernel.org # v7.1 +Signed-off-by: DaeMyung Kang +Reviewed-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/attrib.c | 18 +++++++++++++++++- + 1 file changed, 17 insertions(+), 1 deletion(-) + +--- a/fs/ntfs/attrib.c ++++ b/fs/ntfs/attrib.c +@@ -597,6 +597,22 @@ static u32 ntfs_resident_attr_min_value_ + } + } + ++static bool ntfs_attr_type_is_resident_only(const __le32 type) ++{ ++ switch (type) { ++ case AT_STANDARD_INFORMATION: ++ case AT_FILE_NAME: ++ case AT_OBJECT_ID: ++ case AT_VOLUME_NAME: ++ case AT_VOLUME_INFORMATION: ++ case AT_INDEX_ROOT: ++ case AT_EA_INFORMATION: ++ return true; ++ default: ++ return false; ++ } ++} ++ + static bool ntfs_file_name_attr_value_is_valid(const u8 *value, const u32 value_length) + { + const struct file_name_attr *fn; +@@ -692,7 +708,7 @@ static bool ntfs_attr_value_is_valid(str + u32 min_len; + + if (a->non_resident) { +- if (a->type == AT_FILE_NAME || a->type == AT_VOLUME_NAME) ++ if (ntfs_attr_type_is_resident_only(a->type)) + goto corrupt; + if (!ntfs_non_resident_attr_value_is_valid(a)) + goto corrupt; diff --git a/queue-7.1/ntfs-sanitize-mft-references-returned-from-ntfs_lookup_inode_by_name.patch b/queue-7.1/ntfs-sanitize-mft-references-returned-from-ntfs_lookup_inode_by_name.patch new file mode 100644 index 0000000000..10ae45dbd2 --- /dev/null +++ b/queue-7.1/ntfs-sanitize-mft-references-returned-from-ntfs_lookup_inode_by_name.patch @@ -0,0 +1,93 @@ +From d97a36bae86a9a4021562ded2987f904e6bcb1d7 Mon Sep 17 00:00:00 2001 +From: Namjae Jeon +Date: Mon, 6 Jul 2026 12:00:00 +0900 +Subject: ntfs: sanitize MFT references returned from ntfs_lookup_inode_by_name() + +From: Namjae Jeon + +commit d97a36bae86a9a4021562ded2987f904e6bcb1d7 upstream. + +ntfs_lookup_inode_by_name() returns MFT references read from directory +index entries on disk. These values are untrusted, but the function can +currently return an error-marked MFT reference to its callers without +validating it. + +Callers later decode lookup failures with MREF_ERR(). A crafted NTFS image +can set the MREF error bit while leaving the low bits as an arbitrary +value, causing callers to consume a bogus pseudo-errno instead of treating +the lookup result as corrupted on-disk metadata. + +Fix this at the source by normalizing every error-marked MFT reference +returned from ntfs_lookup_inode_by_name() to ERR_MREF(-EIO). Apply this to +all four directory lookup return paths so every caller gets a validated +result without needing additional checks or an API change. + +This keeps the sanitization in the common lookup helper, which is cleaner +than duplicating validation in each caller. + +Fixes: 1e9ea7e04472 ("Revert "fs: Remove NTFS classic"") +Cc: stable@vger.kernel.org +Reported-by: Hongling Zeng +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/dir.c | 15 +++++++++++---- + 1 file changed, 11 insertions(+), 4 deletions(-) + +diff --git a/fs/ntfs/dir.c b/fs/ntfs/dir.c +index 4b6bd5f30c65..6fa9ae3377cb 100644 +--- a/fs/ntfs/dir.c ++++ b/fs/ntfs/dir.c +@@ -23,6 +23,13 @@ + __le16 I30[5] = { cpu_to_le16('$'), cpu_to_le16('I'), + cpu_to_le16('3'), cpu_to_le16('0'), 0 }; + ++static inline u64 ntfs_check_mref(u64 mref) ++{ ++ if (IS_ERR_MREF(mref)) ++ return ERR_MREF(-EIO); ++ return mref; ++} ++ + /* + * ntfs_lookup_inode_by_name - find an inode in a directory given its name + * @dir_ni: ntfs inode of the directory in which to search for the name +@@ -178,7 +185,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname, + mref = le64_to_cpu(ie->data.dir.indexed_file); + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(dir_ni); +- return mref; ++ return ntfs_check_mref(mref); + } + /* + * For a case insensitive mount, we also perform a case +@@ -273,7 +280,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname, + if (name) { + ntfs_attr_put_search_ctx(ctx); + unmap_mft_record(dir_ni); +- return name->mref; ++ return ntfs_check_mref(name->mref); + } + ntfs_debug("Entry not found."); + err = -ENOENT; +@@ -413,7 +420,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname, + mref = le64_to_cpu(ie->data.dir.indexed_file); + kfree(kaddr); + iput(ia_vi); +- return mref; ++ return ntfs_check_mref(mref); + } + /* + * For a case insensitive mount, we also perform a case +@@ -538,7 +545,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname, + if (name) { + kfree(kaddr); + iput(ia_vi); +- return name->mref; ++ return ntfs_check_mref(name->mref); + } + ntfs_debug("Entry not found."); + err = -ENOENT; +-- +2.55.0 + diff --git a/queue-7.1/ntfs-skip-extent-mft-records-in-writeback-to-prevent-deadlock.patch b/queue-7.1/ntfs-skip-extent-mft-records-in-writeback-to-prevent-deadlock.patch new file mode 100644 index 0000000000..4b9bdc751d --- /dev/null +++ b/queue-7.1/ntfs-skip-extent-mft-records-in-writeback-to-prevent-deadlock.patch @@ -0,0 +1,222 @@ +From 76bc14c7097ff678b2b5dbfd4fa33b46897d87ce Mon Sep 17 00:00:00 2001 +From: Hyunchul Lee +Date: Thu, 21 May 2026 14:37:03 +0900 +Subject: ntfs: skip extent mft records in writeback to prevent deadlock + +From: Hyunchul Lee + +commit 76bc14c7097ff678b2b5dbfd4fa33b46897d87ce upstream. + +This patch fixes the ABBA deadlock between extent_lock and extent +mrec_lock triggered by xfstests generic/113, that occurs since the commit +6994acf33bae ("ntfs: use base mft_no when looking up base inode for + extent record"). + +Path A (inode writeback): + VFS writeback + -> ntfs_write_inode() + -> __ntfs_write_inode() + -> mutex_lock(&ni->extent_lock) + -> mutex_lock(&tni->mrec_lock) + +Path B (MFT folio writeback): + VFS writeback of $MFT dirty folios + -> ntfs_mft_writepages() + -> ntfs_write_mft_block() + -> ntfs_may_write_mft_record() + -> holds one extent mrec_lock from a previous iteration + -> tries to acquire another base inode extent_lock + +By removing all extent_lock and extent mrec_lock acquisition from the MFT +folio writeback path, the ABBA lock ordering is eliminated: + +Path A: __ntfs_write_inode(): extent_lock -> mrec_lock +Path B (removed): ntfs_write_mft_block(): mrec_lock -> extent_lock + +Path B is always redundant for extent records because: + +1. mark_mft_record_dirty(ext_ni) does NOT dirty the MFT folio. + It only sets NInoDirty(ext_ni) and marks the base VFS inode dirty + via __mark_inode_dirty(I_DIRTY_DATASYNC), which triggers Path A. + Therefore, normal extent modifications never create a situation where + the MFT folio is dirty and Path B is not scheduled. + +2. The MFT folio only gets dirtied via ntfs_mft_mark_dirty() inside + ntfs_mft_record_alloc(). But all identified callers in attrib.c + (ntfs_attr_add, ntfs_attr_record_move_away, + ntfs_attr_make_non_resident, ntfs_attr_record_resize) follow through + with mark_mft_record_dirty(), which triggers Path A to write the + complete record. + +3. ntfs_evict_big_inode() calls ntfs_commit_inode() before freeing extent + inodes, ensuring all dirty extents are flushed via Path A before the + base inode leaves the icache. + +Cc: stable@vger.kernel.org # v7.1 +Signed-off-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/mft.c | 129 +--------------------------------------------------------- + 1 file changed, 4 insertions(+), 125 deletions(-) + +--- a/fs/ntfs/mft.c ++++ b/fs/ntfs/mft.c +@@ -743,23 +743,6 @@ static int ntfs_test_inode_wb(struct ino + * + * If the mft record is not a FILE record or it is a base mft record, we can + * safely write it and return 'true'. +- * +- * We now know the mft record is an extent mft record. We check if the inode +- * corresponding to its base mft record is in icache. If it is not, we cannot +- * safely determine the state of the extent inode, so we return 'false'. +- * +- * We now have the base inode for the extent mft record. We check if it has an +- * ntfs inode for the extent mft record attached. If not, it is safe to write +- * the extent mft record and we return 'true'. +- * +- * If the extent inode is attached, we check if it is dirty. If so, we return +- * 'false' (letting the standard write_inode path handle it). +- * +- * If it is not dirty, we attempt to lock the extent mft record. If the lock +- * was already taken, it is not safe to write and we return 'false'. +- * +- * If we manage to obtain the lock we have exclusive access to the extent mft +- * record. We set @locked_ni to the now locked ntfs inode and return 'true'. + */ + static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no, + const struct mft_record *m, struct ntfs_inode **locked_ni, +@@ -768,8 +751,7 @@ static bool ntfs_may_write_mft_record(st + struct super_block *sb = vol->sb; + struct inode *mft_vi = vol->mft_ino; + struct inode *vi; +- struct ntfs_inode *ni, *eni, **extent_nis; +- int i; ++ struct ntfs_inode *ni; + struct ntfs_attr na = {0}; + + ntfs_debug("Entering for inode 0x%llx.", mft_no); +@@ -849,100 +831,10 @@ static bool ntfs_may_write_mft_record(st + mft_no); + return true; + } +- /* +- * This is an extent mft record. Check if the inode corresponding to +- * its base mft record is in icache and obtain a reference to it if it +- * is. +- */ +- na.mft_no = MREF_LE(m->base_mft_record); +- na.state = 0; +- ntfs_debug("Mft record 0x%llx is an extent record. Looking for base inode 0x%llx in icache.", +- mft_no, na.mft_no); +- if (!na.mft_no) { +- /* Balance the below iput(). */ +- vi = igrab(mft_vi); +- WARN_ON(vi != mft_vi); +- } else { +- vi = find_inode_nowait(sb, na.mft_no, ntfs_test_inode_wb, &na); +- if (na.state == NI_BeingDeleted || na.state == NI_BeingCreated) +- return false; +- } + +- if (!vi) +- return false; +- ntfs_debug("Base inode 0x%llx is in icache.", na.mft_no); +- /* +- * The base inode is in icache. Check if it has the extent inode +- * corresponding to this extent mft record attached. +- */ +- ni = NTFS_I(vi); +- mutex_lock(&ni->extent_lock); +- if (ni->nr_extents <= 0) { +- /* +- * The base inode has no attached extent inodes, write this +- * extent mft record. +- */ +- mutex_unlock(&ni->extent_lock); +- *ref_vi = vi; +- ntfs_debug("Base inode 0x%llx has no attached extent inodes, write the extent record.", +- na.mft_no); +- return true; +- } +- /* Iterate over the attached extent inodes. */ +- extent_nis = ni->ext.extent_ntfs_inos; +- for (eni = NULL, i = 0; i < ni->nr_extents; ++i) { +- if (mft_no == extent_nis[i]->mft_no) { +- /* +- * Found the extent inode corresponding to this extent +- * mft record. +- */ +- eni = extent_nis[i]; +- break; +- } +- } +- /* +- * If the extent inode was not attached to the base inode, write this +- * extent mft record. +- */ +- if (!eni) { +- mutex_unlock(&ni->extent_lock); +- *ref_vi = vi; +- ntfs_debug("Extent inode 0x%llx is not attached to its base inode 0x%llx, write the extent record.", +- mft_no, na.mft_no); +- return true; +- } +- ntfs_debug("Extent inode 0x%llx is attached to its base inode 0x%llx.", +- mft_no, na.mft_no); +- /* Take a reference to the extent ntfs inode. */ +- atomic_inc(&eni->count); +- mutex_unlock(&ni->extent_lock); +- +- /* if extent inode is dirty, write_inode will write it */ +- if (NInoDirty(eni)) { +- atomic_dec(&eni->count); +- *ref_vi = vi; +- return false; +- } +- +- /* +- * Found the extent inode coresponding to this extent mft record. +- * Try to take the mft record lock. +- */ +- if (unlikely(!mutex_trylock(&eni->mrec_lock))) { +- atomic_dec(&eni->count); +- *ref_vi = vi; +- ntfs_debug("Extent mft record 0x%llx is already locked, do not write it.", +- mft_no); +- return false; +- } +- ntfs_debug("Managed to lock extent mft record 0x%llx, write it.", +- mft_no); +- /* +- * The write has to occur while we hold the mft record lock so return +- * the locked extent ntfs inode. +- */ +- *locked_ni = eni; +- return true; ++ ntfs_debug("Mft record 0x%llx is an extent record, skip it.", ++ mft_no); ++ return false; + } + + static const char *es = " Leaving inconsistent metadata. Unmount and run chkdsk."; +@@ -2792,19 +2684,6 @@ static int ntfs_write_mft_block(struct f + s64 rl_len = 0; + + /* +- * Skip $MFT extent mft records and let them being written +- * by writeback to avioid deadlocks. the $MFT runlist +- * lock must be taken before $MFT extent mrec_lock is taken. +- */ +- if (tni && tni->nr_extents < 0 && +- tni->ext.base_ntfs_ino == NTFS_I(vol->mft_ino)) { +- mutex_unlock(&tni->mrec_lock); +- atomic_dec(&tni->count); +- iput(vol->mft_ino); +- continue; +- } +- +- /* + * The record should be written. If a locked ntfs + * inode was returned, add it to the array of locked + * ntfs inodes. diff --git a/queue-7.1/ntfs-update-index-root-allocated-size-before-shrink.patch b/queue-7.1/ntfs-update-index-root-allocated-size-before-shrink.patch new file mode 100644 index 0000000000..77de265578 --- /dev/null +++ b/queue-7.1/ntfs-update-index-root-allocated-size-before-shrink.patch @@ -0,0 +1,72 @@ +From e782ca90ceb798bb1811b214bf814216f11aae6a Mon Sep 17 00:00:00 2001 +From: DaeMyung Kang +Date: Tue, 9 Jun 2026 00:49:16 +0900 +Subject: ntfs: update index root allocated size before shrink + +From: DaeMyung Kang + +commit e782ca90ceb798bb1811b214bf814216f11aae6a upstream. + +ntfs_ir_truncate() currently shrinks the resident $INDEX_ROOT value first +and only updates index.allocated_size after re-looking up the attribute. +During that relookup, the resident value_length can already be smaller +while index.allocated_size still contains the old larger size. + +That leaves a transiently inconsistent $INDEX_ROOT layout and prevents +lookup-time $INDEX_ROOT validation from being enabled: validation can +correctly reject allocated_size extending past the newly shrunk resident +value. + +When shrinking, lower index.allocated_size before shrinking value_length. +If the truncate fails, restore the old allocated_size. Keep the existing +grow ordering because the old allocated_size remains within the enlarged +resident value until it is updated after the relookup. The shrink path is +safe because the new value_length still covers struct index_root, so the +index.allocated_size field remains present while it is updated first. + +Cc: stable@vger.kernel.org # v7.1 +Signed-off-by: DaeMyung Kang +Reviewed-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/index.c | 18 +++++++++++++++--- + 1 file changed, 15 insertions(+), 3 deletions(-) + +--- a/fs/ntfs/index.c ++++ b/fs/ntfs/index.c +@@ -1342,9 +1342,16 @@ out: + static int ntfs_ir_truncate(struct ntfs_index_context *icx, int data_size) + { + int ret; ++ u32 old_allocated_size; ++ bool shrink; + + ntfs_debug("Entering\n"); + ++ old_allocated_size = le32_to_cpu(icx->ir->index.allocated_size); ++ shrink = data_size < old_allocated_size; ++ if (shrink) ++ icx->ir->index.allocated_size = cpu_to_le32(data_size); ++ + /* + * INDEX_ROOT must be resident and its entries can be moved to + * struct index_block, so ENOSPC isn't a real error. +@@ -1356,9 +1363,14 @@ static int ntfs_ir_truncate(struct ntfs_ + if (!icx->ir) + return -ENOENT; + +- icx->ir->index.allocated_size = cpu_to_le32(data_size); +- } else if (ret != -ENOSPC) +- ntfs_error(icx->idx_ni->vol->sb, "Failed to truncate INDEX_ROOT"); ++ if (!shrink) ++ icx->ir->index.allocated_size = cpu_to_le32(data_size); ++ } else { ++ if (shrink) ++ icx->ir->index.allocated_size = cpu_to_le32(old_allocated_size); ++ if (ret != -ENOSPC) ++ ntfs_error(icx->idx_ni->vol->sb, "Failed to truncate INDEX_ROOT"); ++ } + + return ret; + } diff --git a/queue-7.1/ntfs-validate-attribute-values-on-lookup.patch b/queue-7.1/ntfs-validate-attribute-values-on-lookup.patch new file mode 100644 index 0000000000..ffa1e866df --- /dev/null +++ b/queue-7.1/ntfs-validate-attribute-values-on-lookup.patch @@ -0,0 +1,285 @@ +From d5803e3345dae9c6470bb61869885236276b9a35 Mon Sep 17 00:00:00 2001 +From: DaeMyung Kang +Date: Sat, 30 May 2026 23:35:09 +0900 +Subject: ntfs: validate attribute values on lookup + +From: DaeMyung Kang + +commit d5803e3345dae9c6470bb61869885236276b9a35 upstream. + +ntfs_attr_find() and ntfs_external_attr_find() check that generic +resident attribute values fit in their attribute records and that +fixed-size resident values are large enough. For variable-length resident +formats, however, the fixed part is not enough: embedded length fields +can still point callers past the resident value. + +A crafted image can set a small resident $FILE_NAME value_length while +leaving file_name_length large. Callers then trust file_name_length and +read past the resident value when converting or comparing the name. This +was reproduced with a crafted image under KASAN as a slab-out-of-bounds +read from the kmalloc-1k MFT record copy. The stack included +ntfs_lookup(), ntfs_iget(), ntfs_read_locked_inode(), ntfs_attr_name_get(), +ntfs_ucstonls(), and utf16s_to_utf8s(). + +Add a shared attribute value validator and use it before a lookup path +can return an attribute, including the AT_UNUSED enumeration case where +callers inspect returned attributes directly. The helper validates +resident value bounds, minimum resident value sizes, variable-length +$FILE_NAME fields, and non-resident mapping-pairs metadata that was +previously checked separately in both lookup paths. + +This also preserves the intended resident @val matching semantics in the +external attribute lookup path. The old duplicated validation block +overwrote the actual resident value length with the type-specific minimum +length before comparing @val, so variable-length resident values could +fail to match even when the bytes were identical. Keep the comparison on +the actual value length, and make ntfs_attrlist_entry_add() compare +resident attributes with lowest_vcn zero instead of reading the +non-resident union member after a successful resident match. + +Reject non-resident $FILE_NAME records too: the format requires +$FILE_NAME to be resident and callers treat returned records as resident. + +Cc: stable@vger.kernel.org # v7.1 +Fixes: 6ceb4cc81ef3 ("ntfs: add bound checking to ntfs_attr_find") +Signed-off-by: DaeMyung Kang +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/attrib.c | 160 ++++++++++++++++++++++++++++++++--------------------- + fs/ntfs/attrlist.c | 11 ++- + 2 files changed, 107 insertions(+), 64 deletions(-) + +--- a/fs/ntfs/attrib.c ++++ b/fs/ntfs/attrib.c +@@ -595,6 +595,97 @@ static u32 ntfs_resident_attr_min_value_ + } + } + ++static bool ntfs_file_name_attr_value_is_valid(const u8 *value, const u32 value_length) ++{ ++ const struct file_name_attr *fn; ++ u32 file_name_size; ++ ++ fn = (const struct file_name_attr *)value; ++ file_name_size = fn->file_name_length * sizeof(__le16); ++ ++ return file_name_size <= ++ value_length - offsetof(struct file_name_attr, file_name); ++} ++ ++struct ntfs_resident_attr_value { ++ const u8 *data; ++ u32 len; ++}; ++ ++static bool ntfs_resident_attr_value_get(const struct attr_record *a, ++ struct ntfs_resident_attr_value *value) ++{ ++ u32 attr_len; ++ u16 value_offset; ++ ++ attr_len = le32_to_cpu(a->length); ++ if (attr_len < offsetof(struct attr_record, data.resident.reserved) + ++ sizeof(a->data.resident.reserved)) ++ return false; ++ ++ value->len = le32_to_cpu(a->data.resident.value_length); ++ value_offset = le16_to_cpu(a->data.resident.value_offset); ++ ++ if (value->len > attr_len || value_offset > attr_len - value->len) ++ return false; ++ ++ value->data = (const u8 *)a + value_offset; ++ return true; ++} ++ ++static bool ntfs_non_resident_attr_value_is_valid(const struct attr_record *a) ++{ ++ u32 attr_len; ++ u32 min_len; ++ u16 mp_offset; ++ ++ attr_len = le32_to_cpu(a->length); ++ min_len = offsetof(struct attr_record, data.non_resident.initialized_size) + ++ sizeof(a->data.non_resident.initialized_size); ++ if (attr_len < min_len) ++ return false; ++ ++ mp_offset = le16_to_cpu(a->data.non_resident.mapping_pairs_offset); ++ return mp_offset >= min_len && mp_offset <= attr_len; ++} ++ ++static bool ntfs_attr_value_is_valid(struct ntfs_volume *vol, ++ const struct attr_record *a, ++ const u64 mft_no) ++{ ++ struct ntfs_resident_attr_value value; ++ u32 min_len; ++ ++ if (a->non_resident) { ++ if (a->type == AT_FILE_NAME) ++ goto corrupt; ++ if (!ntfs_non_resident_attr_value_is_valid(a)) ++ goto corrupt; ++ return true; ++ } ++ ++ if (!ntfs_resident_attr_value_get(a, &value)) ++ goto corrupt; ++ ++ min_len = ntfs_resident_attr_min_value_length(a->type); ++ if (min_len && value.len < min_len) ++ goto corrupt; ++ ++ switch (a->type) { ++ case AT_FILE_NAME: ++ if (!ntfs_file_name_attr_value_is_valid(value.data, value.len)) ++ goto corrupt; ++ break; ++ } ++ return true; ++ ++corrupt: ++ ntfs_error(vol->sb, ++ "Corrupt %#x attribute in MFT record %llu\n", ++ le32_to_cpu(a->type), mft_no); ++ return false; ++} ++ + /* + * ntfs_attr_find - find (next) attribute in mft record + * @type: attribute type to find +@@ -705,8 +796,11 @@ static int ntfs_attr_find(const __le32 t + } + } + +- if (type == AT_UNUSED) ++ if (type == AT_UNUSED) { ++ if (!ntfs_attr_value_is_valid(vol, a, ctx->ntfs_ino->mft_no)) ++ break; + return 0; ++ } + if (a->type != type) + continue; + /* +@@ -747,37 +841,8 @@ static int ntfs_attr_find(const __le32 t + } + } + +- /* Validate attribute's value offset/length */ +- if (!a->non_resident) { +- u32 min_len; +- u32 value_length = le32_to_cpu(a->data.resident.value_length); +- u16 value_offset = le16_to_cpu(a->data.resident.value_offset); +- +- if (value_length > le32_to_cpu(a->length) || +- value_offset > le32_to_cpu(a->length) - value_length) +- break; +- +- min_len = ntfs_resident_attr_min_value_length(a->type); +- if (min_len && value_length < min_len) { +- ntfs_error(vol->sb, +- "Too small %#x resident attribute value in MFT record %lld\n", +- le32_to_cpu(a->type), (long long)ctx->ntfs_ino->mft_no); +- break; +- } +- } else { +- u32 min_len; +- u16 mp_offset; +- +- min_len = offsetof(struct attr_record, data.non_resident.initialized_size) + +- sizeof(a->data.non_resident.initialized_size); +- if (le32_to_cpu(a->length) < min_len) +- break; +- +- mp_offset = le16_to_cpu(a->data.non_resident.mapping_pairs_offset); +- if (mp_offset < min_len || +- mp_offset > le32_to_cpu(a->length)) +- break; +- } ++ if (!ntfs_attr_value_is_valid(vol, a, ctx->ntfs_ino->mft_no)) ++ break; + + /* + * The names match or @name not present and attribute is +@@ -1299,22 +1364,8 @@ do_next_attr_loop: + + ctx->attr = a; + +- if (a->non_resident) { +- u32 min_len; +- u16 mp_offset; +- +- min_len = offsetof(struct attr_record, +- data.non_resident.initialized_size) + +- sizeof(a->data.non_resident.initialized_size); +- +- if (le32_to_cpu(a->length) < min_len) +- break; +- +- mp_offset = +- le16_to_cpu(a->data.non_resident.mapping_pairs_offset); +- if (mp_offset < min_len || mp_offset > attr_len) +- break; +- } ++ if (!ntfs_attr_value_is_valid(vol, a, ctx->ntfs_ino->mft_no)) ++ break; + + /* + * If no @val specified or @val specified and it matches, we +@@ -1326,19 +1377,6 @@ do_next_attr_loop: + u32 value_length = le32_to_cpu(a->data.resident.value_length); + u16 value_offset = le16_to_cpu(a->data.resident.value_offset); + +- if (attr_len < offsetof(struct attr_record, data.resident.reserved) + +- sizeof(a->data.resident.reserved)) +- break; +- if (value_length > attr_len || value_offset > attr_len - value_length) +- break; +- +- value_length = ntfs_resident_attr_min_value_length(a->type); +- if (value_length && le32_to_cpu(a->data.resident.value_length) < +- value_length) { +- pr_err("Too small resident attribute value in MFT record %lld, type %#x\n", +- (long long)ctx->ntfs_ino->mft_no, a->type); +- break; +- } + if (value_length == val_len && + !memcmp((u8 *)a + value_offset, val, val_len)) { + attr_found: +--- a/fs/ntfs/attrlist.c ++++ b/fs/ntfs/attrlist.c +@@ -118,6 +118,7 @@ int ntfs_attrlist_entry_add(struct ntfs_ + int entry_len, entry_offset, err; + struct mft_record *ni_mrec; + u8 *old_al; ++ __le64 lowest_vcn; + + if (!ni || !attr) { + ntfs_debug("Invalid arguments.\n"); +@@ -158,17 +159,21 @@ int ntfs_attrlist_entry_add(struct ntfs_ + ntfs_error(ni->vol->sb, "Failed to get search context"); + goto err_out; + } ++ if (attr->non_resident) ++ lowest_vcn = attr->data.non_resident.lowest_vcn; ++ else ++ lowest_vcn = 0; + + err = ntfs_attr_lookup(attr->type, (attr->name_length) ? (__le16 *) + ((u8 *)attr + le16_to_cpu(attr->name_offset)) : + AT_UNNAMED, attr->name_length, CASE_SENSITIVE, +- (attr->non_resident) ? le64_to_cpu(attr->data.non_resident.lowest_vcn) : +- 0, (attr->non_resident) ? NULL : ((u8 *)attr + ++ le64_to_cpu(lowest_vcn), ++ (attr->non_resident) ? NULL : ((u8 *)attr + + le16_to_cpu(attr->data.resident.value_offset)), (attr->non_resident) ? + 0 : le32_to_cpu(attr->data.resident.value_length), ctx); + if (!err) { + /* Found some extent, check it to be before new extent. */ +- if (ctx->al_entry->lowest_vcn == attr->data.non_resident.lowest_vcn) { ++ if (ctx->al_entry->lowest_vcn == lowest_vcn) { + err = -EEXIST; + ntfs_debug("Such attribute already present in the attribute list.\n"); + ntfs_attr_put_search_ctx(ctx); diff --git a/queue-7.1/ntfs-validate-index-block-header-more-strictly.patch b/queue-7.1/ntfs-validate-index-block-header-more-strictly.patch new file mode 100644 index 0000000000..507747d3e7 --- /dev/null +++ b/queue-7.1/ntfs-validate-index-block-header-more-strictly.patch @@ -0,0 +1,246 @@ +From 14bc34fe948523dc2b0174691f9af9e74eb4f3fd Mon Sep 17 00:00:00 2001 +From: Hyunchul Lee +Date: Sat, 23 May 2026 13:14:20 +0900 +Subject: ntfs: validate index block header more strictly + +From: Hyunchul Lee + +commit 14bc34fe948523dc2b0174691f9af9e74eb4f3fd upstream. + +Modify ntfs_index_block_inconsisent() to perform stricter validation of +INDEX_HEADER geometry in INDX blocks, and update +ntfs_lookup_inode_by_name() to use that function to validate INDX +blocks. + +Cc: stable@vger.kernel.org # v7.1 +Tested-by: woot000 +Signed-off-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/dir.c | 38 +++----------------- + fs/ntfs/index.c | 103 +++++++++++++++++++++++++++++++++++++++----------------- + fs/ntfs/index.h | 3 + + 3 files changed, 82 insertions(+), 62 deletions(-) + +--- a/fs/ntfs/dir.c ++++ b/fs/ntfs/dir.c +@@ -342,43 +342,19 @@ fast_descend_into_child_node: + dir_ni->mft_no); + goto unm_err_out; + } +- /* Catch multi sector transfer fixup errors. */ +- if (unlikely(!ntfs_is_indx_record(ia->magic))) { +- ntfs_error(sb, +- "Directory index record with vcn 0x%llx is corrupt. Corrupt inode 0x%llx. Run chkdsk.", +- vcn, dir_ni->mft_no); +- goto unm_err_out; +- } +- if (le64_to_cpu(ia->index_block_vcn) != vcn) { +- ntfs_error(sb, +- "Actual VCN (0x%llx) of index buffer is different from expected VCN (0x%llx). Directory inode 0x%llx is corrupt or driver bug.", +- le64_to_cpu(ia->index_block_vcn), +- vcn, dir_ni->mft_no); +- goto unm_err_out; +- } +- if (le32_to_cpu(ia->index.allocated_size) + 0x18 != +- dir_ni->itype.index.block_size) { +- ntfs_error(sb, +- "Index buffer (VCN 0x%llx) of directory inode 0x%llx has a size (%u) differing from the directory specified size (%u). Directory inode is corrupt or driver bug.", +- vcn, dir_ni->mft_no, +- le32_to_cpu(ia->index.allocated_size) + 0x18, +- dir_ni->itype.index.block_size); +- goto unm_err_out; +- } + index_end = (u8 *)ia + dir_ni->itype.index.block_size; + if (index_end > kaddr + PAGE_SIZE) { + ntfs_error(sb, +- "Index buffer (VCN 0x%llx) of directory inode 0x%llx crosses page boundary. Impossible! Cannot access! This is probably a bug in the driver.", +- vcn, dir_ni->mft_no); ++ "Index buffer (VCN 0x%llx) of directory inode 0x%llx crosses page boundary. Impossible! Cannot access! This is probably a bug in the driver.", ++ vcn, dir_ni->mft_no); + goto unm_err_out; + } +- index_end = (u8 *)&ia->index + le32_to_cpu(ia->index.index_length); +- if (index_end > (u8 *)ia + dir_ni->itype.index.block_size) { +- ntfs_error(sb, +- "Size of index buffer (VCN 0x%llx) of directory inode 0x%llx exceeds maximum size.", +- vcn, dir_ni->mft_no); ++ err = ntfs_index_block_inconsistent(vol, ia, ++ dir_ni->itype.index.block_size, ++ vcn, dir_ni->mft_no); ++ if (err) + goto unm_err_out; +- } ++ index_end = (u8 *)&ia->index + le32_to_cpu(ia->index.index_length); + /* The first index entry. */ + ie = (struct index_entry *)((u8 *)&ia->index + + le32_to_cpu(ia->index.entries_offset)); +--- a/fs/ntfs/index.c ++++ b/fs/ntfs/index.c +@@ -306,6 +306,55 @@ static int ntfs_ie_end(struct index_entr + return ie->flags & INDEX_ENTRY_END || !ie->length; + } + ++static int ntfs_index_header_inconsistent(struct ntfs_volume *vol, ++ const struct index_header *ih, ++ u32 bytes_available, u64 inum) ++{ ++ u32 entries_offset, index_length, allocated_size; ++ ++ if (bytes_available < sizeof(struct index_header)) { ++ ntfs_error(vol->sb, ++ "index block in inode %llu is smaller than an index header.", ++ (unsigned long long)inum); ++ return -EIO; ++ } ++ ++ entries_offset = le32_to_cpu(ih->entries_offset); ++ index_length = le32_to_cpu(ih->index_length); ++ allocated_size = le32_to_cpu(ih->allocated_size); ++ ++ if (entries_offset < sizeof(struct index_header) || ++ entries_offset > bytes_available) { ++ ntfs_error(vol->sb, ++ "Invalid index entry offset in inode %llu.", ++ (unsigned long long)inum); ++ return -EIO; ++ } ++ ++ if (index_length <= entries_offset) { ++ ntfs_error(vol->sb, ++ "No space for index entries in inode %llu.", ++ (unsigned long long)inum); ++ return -EIO; ++ } ++ ++ if (allocated_size < index_length) { ++ ntfs_error(vol->sb, ++ "Index entries overflow in inode %llu.", ++ (unsigned long long)inum); ++ return -EIO; ++ } ++ ++ if (allocated_size > bytes_available || index_length > bytes_available) { ++ ntfs_error(vol->sb, ++ "Index entries in inode %llu exceed the available buffer.", ++ (unsigned long long)inum); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ + /* + * Find the last entry in the index block + */ +@@ -440,7 +489,7 @@ static struct index_entry *ntfs_ie_dup_n + * The size of block is assumed to have been checked to be what is + * defined in the index root. + * +- * Returns 0 if no error was found -1 otherwise (with errno unchanged) ++ * Returns 0 if no error was found, -EIO otherwise + * + * |<--->| offsetof(struct index_block, index) + * | |<--->| sizeof(struct index_header) +@@ -455,21 +504,20 @@ static struct index_entry *ntfs_ie_dup_n + * + * size(struct index_header) <= ent_offset < ind_length <= alloc_size < bk_size + */ +-static int ntfs_index_block_inconsistent(struct ntfs_index_context *icx, +- struct index_block *ib, s64 vcn) ++int ntfs_index_block_inconsistent(struct ntfs_volume *vol, ++ const struct index_block *ib, ++ u32 block_size, s64 vcn, u64 inum) + { + u32 ib_size = (unsigned int)le32_to_cpu(ib->index.allocated_size) + + offsetof(struct index_block, index); +- struct super_block *sb = icx->idx_ni->vol->sb; +- unsigned long long inum = icx->idx_ni->mft_no; ++ struct super_block *sb = vol->sb; + + ntfs_debug("Entering\n"); + + if (!ntfs_is_indx_record(ib->magic)) { +- + ntfs_error(sb, "Corrupt index block signature: vcn %lld inode %llu\n", +- vcn, (unsigned long long)icx->idx_ni->mft_no); +- return -1; ++ vcn, (unsigned long long)inum); ++ return -EIO; + } + + if (le64_to_cpu(ib->index_block_vcn) != vcn) { +@@ -477,31 +525,22 @@ static int ntfs_index_block_inconsistent + "Corrupt index block: s64 (%lld) is different from expected s64 (%lld) in inode %llu\n", + (long long)le64_to_cpu(ib->index_block_vcn), + vcn, inum); +- return -1; ++ return -EIO; + } + +- if (ib_size != icx->block_size) { ++ if (ib_size != block_size) { + ntfs_error(sb, +- "Corrupt index block : s64 (%lld) of inode %llu has a size (%u) differing from the index specified size (%u)\n", +- vcn, inum, ib_size, icx->block_size); +- return -1; +- } +- +- if (le32_to_cpu(ib->index.entries_offset) < sizeof(struct index_header)) { +- ntfs_error(sb, "Invalid index entry offset in inode %lld\n", inum); +- return -1; +- } +- if (le32_to_cpu(ib->index.index_length) <= +- le32_to_cpu(ib->index.entries_offset)) { +- ntfs_error(sb, "No space for index entries in inode %lld\n", inum); +- return -1; +- } +- if (le32_to_cpu(ib->index.allocated_size) < +- le32_to_cpu(ib->index.index_length)) { +- ntfs_error(sb, "Index entries overflow in inode %lld\n", inum); +- return -1; ++ "Corrupt index block : s64 (%lld) of inode %llu has a size (%u) differing from the index specified size (%u)\n", ++ vcn, inum, ib_size, block_size); ++ return -EIO; + } + ++ if (ntfs_index_header_inconsistent(vol, &ib->index, ++ block_size - ++ offsetof(struct index_block, index), ++ inum)) ++ return -EIO; ++ + return 0; + } + +@@ -668,12 +707,14 @@ static int ntfs_ib_read(struct ntfs_inde + else + ntfs_error(icx->idx_ni->vol->sb, + "Failed to read full index block at %lld\n", pos); +- return -1; ++ return -EIO; + } + + post_read_mst_fixup((struct ntfs_record *)((u8 *)dst), icx->block_size); +- if (ntfs_index_block_inconsistent(icx, dst, vcn)) +- return -1; ++ if (ntfs_index_block_inconsistent(icx->idx_ni->vol, dst, ++ icx->block_size, vcn, ++ icx->idx_ni->mft_no)) ++ return -EIO; + + return 0; + } +--- a/fs/ntfs/index.h ++++ b/fs/ntfs/index.h +@@ -89,6 +89,9 @@ struct ntfs_index_context { + bool sync_write; + }; + ++int ntfs_index_block_inconsistent(struct ntfs_volume *vol, ++ const struct index_block *ib, ++ u32 block_size, s64 vcn, u64 inum); + int ntfs_index_entry_inconsistent(struct ntfs_index_context *icx, struct ntfs_volume *vol, + const struct index_entry *ie, __le32 collation_rule, u64 inum); + struct ntfs_index_context *ntfs_index_ctx_get(struct ntfs_inode *ni, __le16 *name, diff --git a/queue-7.1/ntfs-validate-index-entries-on-reading.patch b/queue-7.1/ntfs-validate-index-entries-on-reading.patch new file mode 100644 index 0000000000..084906dd8e --- /dev/null +++ b/queue-7.1/ntfs-validate-index-entries-on-reading.patch @@ -0,0 +1,263 @@ +From 2221b691d7b2e17f08153f95848dacaa5d87e21d Mon Sep 17 00:00:00 2001 +From: Hyunchul Lee +Date: Sat, 23 May 2026 13:14:22 +0900 +Subject: ntfs: validate index entries on reading + +From: Hyunchul Lee + +commit 2221b691d7b2e17f08153f95848dacaa5d87e21d upstream. + +Validate index entries immediately after reading an index root or index +block from disk. This eliminates repeated checks in lookup and readdir, +and reduce the risk of missing checks in those paths. + +Cc: stable@vger.kernel.org # v7.1 +Tested-by: woot000 +Signed-off-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/dir.c | 28 +----------------- + fs/ntfs/index.c | 86 ++++++++++++++++++++++++++++++-------------------------- + fs/ntfs/index.h | 8 +++-- + fs/ntfs/inode.c | 8 +++-- + 4 files changed, 61 insertions(+), 69 deletions(-) + +--- a/fs/ntfs/dir.c ++++ b/fs/ntfs/dir.c +@@ -135,10 +135,6 @@ u64 ntfs_lookup_inode_by_name(struct ntf + /* Key length should not be zero if it is not last entry. */ + if (!ie->key_length) + goto dir_err_out; +- /* Check the consistency of an index entry */ +- if (ntfs_index_entry_inconsistent(NULL, vol, ie, COLLATION_FILE_NAME, +- dir_ni->mft_no)) +- goto dir_err_out; + /* + * We perform a case sensitive comparison and if that matches + * we are done and return the mft reference of the inode (i.e. +@@ -351,7 +347,8 @@ fast_descend_into_child_node: + } + err = ntfs_index_block_inconsistent(vol, ia, + dir_ni->itype.index.block_size, +- vcn, dir_ni->mft_no); ++ vcn, COLLATION_FILE_NAME, ++ dir_ni->mft_no); + if (err) + goto unm_err_out; + index_end = (u8 *)&ia->index + le32_to_cpu(ia->index.index_length); +@@ -364,15 +361,6 @@ fast_descend_into_child_node: + * reach the last entry. + */ + for (;; ie = (struct index_entry *)((u8 *)ie + le16_to_cpu(ie->length))) { +- /* Bounds checks. */ +- if ((u8 *)ie < (u8 *)ia || +- (u8 *)ie + sizeof(struct index_entry_header) > index_end || +- (u8 *)ie + sizeof(struct index_entry_header) + le16_to_cpu(ie->key_length) > +- index_end || (u8 *)ie + le16_to_cpu(ie->length) > index_end) { +- ntfs_error(sb, "Index entry out of bounds in directory inode 0x%llx.", +- dir_ni->mft_no); +- goto unm_err_out; +- } + /* + * The last entry cannot contain a name. It can however contain + * a pointer to a child node in the B+tree so we just break out. +@@ -382,10 +370,6 @@ fast_descend_into_child_node: + /* Key length should not be zero if it is not last entry. */ + if (!ie->key_length) + goto unm_err_out; +- /* Check the consistency of an index entry */ +- if (ntfs_index_entry_inconsistent(NULL, vol, ie, COLLATION_FILE_NAME, +- dir_ni->mft_no)) +- goto unm_err_out; + /* + * We perform a case sensitive comparison and if that matches + * we are done and return the mft reference of the inode (i.e. +@@ -868,6 +852,7 @@ static int ntfs_readdir(struct file *fil + ictx->vcn_size_bits = vol->cluster_size_bits; + else + ictx->vcn_size_bits = NTFS_BLOCK_SIZE_BITS; ++ ictx->cr = ir->collation_rule; + + /* The first index entry. */ + next = (struct index_entry *)((u8 *)&ir->index + +@@ -905,13 +890,6 @@ static int ntfs_readdir(struct file *fil + if (!next) + break; + nextdir: +- /* Check the consistency of an index entry */ +- if (ntfs_index_entry_inconsistent(ictx, vol, next, COLLATION_FILE_NAME, +- ndir->mft_no)) { +- err = -EIO; +- goto out; +- } +- + if (ie_pos < actor->pos) { + ie_pos += le16_to_cpu(next->length); + continue; +--- a/fs/ntfs/index.c ++++ b/fs/ntfs/index.c +@@ -28,41 +28,10 @@ + * length must have been checked beforehand to not overflow from the + * index record. + */ +-int ntfs_index_entry_inconsistent(struct ntfs_index_context *icx, +- struct ntfs_volume *vol, const struct index_entry *ie, +- __le32 collation_rule, u64 inum) +-{ +- if (icx) { +- struct index_header *ih; +- u8 *ie_start, *ie_end; +- +- if (icx->is_in_root) +- ih = &icx->ir->index; +- else +- ih = &icx->ib->index; +- +- if ((le32_to_cpu(ih->index_length) > le32_to_cpu(ih->allocated_size)) || +- (le32_to_cpu(ih->index_length) > icx->block_size)) { +- ntfs_error(vol->sb, "%s Index entry(0x%p)'s length is too big.", +- icx->is_in_root ? "Index root" : "Index block", +- (u8 *)icx->entry); +- return -EINVAL; +- } +- +- ie_start = (u8 *)ih + le32_to_cpu(ih->entries_offset); +- ie_end = (u8 *)ih + le32_to_cpu(ih->index_length); +- +- if (ie_start > (u8 *)ie || +- ie_end <= (u8 *)ie + le16_to_cpu(ie->length) || +- le16_to_cpu(ie->length) > le32_to_cpu(ih->allocated_size) || +- le16_to_cpu(ie->length) > icx->block_size) { +- ntfs_error(vol->sb, "Index entry(0x%p) is out of range from %s", +- (u8 *)icx->entry, +- icx->is_in_root ? "index root" : "index block"); +- return -EIO; +- } +- } +- ++static int ntfs_index_entry_inconsistent(const struct ntfs_volume *vol, ++ const struct index_entry *ie, ++ __le32 collation_rule, u64 inum) ++{ + if (ie->key_length && + ((le16_to_cpu(ie->key_length) + offsetof(struct index_entry, key)) > + le16_to_cpu(ie->length))) { +@@ -355,6 +324,44 @@ static int ntfs_index_header_inconsisten + return 0; + } + ++int ntfs_index_entries_inconsistent(const struct ntfs_volume *vol, ++ const struct index_header *ih, ++ __le32 collation_rule, u64 inum) ++{ ++ struct index_entry *ie; ++ u8 *index_end = (u8 *)ih + le32_to_cpu(ih->index_length); ++ ++ for (ie = ntfs_ie_get_first((struct index_header *)ih); ++ ; ie = ntfs_ie_get_next(ie)) { ++ if ((u8 *)ie + sizeof(struct index_entry_header) > index_end || ++ (u8 *)ie + le16_to_cpu(ie->length) > index_end) { ++ ntfs_error(vol->sb, ++ "Index entry out of bounds in inode %llu.", ++ (unsigned long long)inum); ++ return -EIO; ++ } ++ ++ if (le16_to_cpu(ie->length) < sizeof(struct index_entry_header)) { ++ ntfs_error(vol->sb, ++ "Index etnry too small in inode %llu.", ++ inum); ++ return -EIO; ++ } ++ ++ if (ntfs_ie_end(ie)) ++ break; ++ ++ if (!ie->key_length) ++ return -EIO; ++ ++ if (ntfs_index_entry_inconsistent(vol, ie, ++ collation_rule, inum)) ++ return -EIO; ++ } ++ ++ return 0; ++} ++ + /* + * Find the last entry in the index block + */ +@@ -506,7 +513,8 @@ static struct index_entry *ntfs_ie_dup_n + */ + int ntfs_index_block_inconsistent(struct ntfs_volume *vol, + const struct index_block *ib, +- u32 block_size, s64 vcn, u64 inum) ++ u32 block_size, s64 vcn, __le32 cr, ++ u64 inum) + { + u32 ib_size = (unsigned int)le32_to_cpu(ib->index.allocated_size) + + offsetof(struct index_block, index); +@@ -540,7 +548,8 @@ int ntfs_index_block_inconsistent(struct + offsetof(struct index_block, index), + inum)) + return -EIO; +- ++ if (ntfs_index_entries_inconsistent(vol, &ib->index, cr, inum)) ++ return -EIO; + return 0; + } + +@@ -730,10 +739,9 @@ static int ntfs_ib_read(struct ntfs_inde + + post_read_mst_fixup((struct ntfs_record *)((u8 *)dst), icx->block_size); + if (ntfs_index_block_inconsistent(icx->idx_ni->vol, dst, +- icx->block_size, vcn, ++ icx->block_size, vcn, icx->cr, + icx->idx_ni->mft_no)) + return -EIO; +- + return 0; + } + +--- a/fs/ntfs/index.h ++++ b/fs/ntfs/index.h +@@ -94,9 +94,11 @@ int ntfs_index_root_inconsistent(struct + const struct index_root *ir, u64 inum); + int ntfs_index_block_inconsistent(struct ntfs_volume *vol, + const struct index_block *ib, +- u32 block_size, s64 vcn, u64 inum); +-int ntfs_index_entry_inconsistent(struct ntfs_index_context *icx, struct ntfs_volume *vol, +- const struct index_entry *ie, __le32 collation_rule, u64 inum); ++ u32 block_size, s64 vcn, ++ __le32 cr, u64 inum); ++int ntfs_index_entries_inconsistent(const struct ntfs_volume *vol, ++ const struct index_header *ih, ++ __le32 collation_rule, u64 inum); + struct ntfs_index_context *ntfs_index_ctx_get(struct ntfs_inode *ni, __le16 *name, + u32 name_len); + void ntfs_index_ctx_put(struct ntfs_index_context *ictx); +--- a/fs/ntfs/inode.c ++++ b/fs/ntfs/inode.c +@@ -945,7 +945,9 @@ view_index_meta: + } + ir = (struct index_root *)((u8 *)a + + le16_to_cpu(a->data.resident.value_offset)); +- if (ntfs_index_root_inconsistent(ni->vol, a, ir, ni->mft_no)) { ++ if (ntfs_index_root_inconsistent(ni->vol, a, ir, ni->mft_no) || ++ ntfs_index_entries_inconsistent(ni->vol, &ir->index, ++ ir->collation_rule, ni->mft_no)) { + ntfs_error(vi->i_sb, "Directory index is corrupt."); + goto unm_err_out; + } +@@ -1538,7 +1540,9 @@ static int ntfs_read_locked_index_inode( + } + + ir = (struct index_root *)((u8 *)a + le16_to_cpu(a->data.resident.value_offset)); +- if (ntfs_index_root_inconsistent(vol, a, ir, ni->mft_no)) { ++ if (ntfs_index_root_inconsistent(vol, a, ir, ni->mft_no) || ++ ntfs_index_entries_inconsistent(vol, &ir->index, ++ ir->collation_rule, ni->mft_no)) { + ntfs_error(vi->i_sb, "Index is corrupt."); + goto unm_err_out; + } diff --git a/queue-7.1/ntfs-validate-resident-index-root-values-on-lookup.patch b/queue-7.1/ntfs-validate-resident-index-root-values-on-lookup.patch new file mode 100644 index 0000000000..dec1742a6c --- /dev/null +++ b/queue-7.1/ntfs-validate-resident-index-root-values-on-lookup.patch @@ -0,0 +1,94 @@ +From fcf5bf0e8570798970e3ae8c95d04765ba2c5b97 Mon Sep 17 00:00:00 2001 +From: DaeMyung Kang +Date: Tue, 9 Jun 2026 00:49:17 +0900 +Subject: ntfs: validate resident index root values on lookup + +From: DaeMyung Kang + +commit fcf5bf0e8570798970e3ae8c95d04765ba2c5b97 upstream. + +Resident $INDEX_ROOT values carry index header fields that callers +consume after lookup. Some callers already validate parts of the layout +before walking entries, but those checks are scattered and do not cover +all root header invariants, such as entries_offset alignment and lower +bound, index_length, and allocated_size consistency. + +The resident root resize paths now keep these header fields consistent +while the value size changes: ntfs_ir_truncate() lowers +index.allocated_size before shrinking the resident value, and +ntfs_ir_reparent() grows the resident value before publishing a larger +root header. Lookup-time validation can therefore cover these invariants +without tripping over the driver's own resize paths. + +Add $INDEX_ROOT to the minimum resident value size table and validate the +resident index header fields before returning the attribute from lookup. +Require 8-byte aligned index header fields, a sane entries_offset, an +index_length within allocated_size, allocated_size within the resident +value, and enough entry space for at least an index entry header. + +The shared validator already rejects non-resident records for +resident-only attribute types, including $INDEX_ROOT. + +Cc: stable@vger.kernel.org # v7.1 +Signed-off-by: DaeMyung Kang +Reviewed-by: Hyunchul Lee +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/attrib.c | 31 +++++++++++++++++++++++++++++++ + 1 file changed, 31 insertions(+) + +--- a/fs/ntfs/attrib.c ++++ b/fs/ntfs/attrib.c +@@ -588,6 +588,8 @@ static u32 ntfs_resident_attr_min_value_ + sizeof(__le16) * 1; + case AT_VOLUME_INFORMATION: + return sizeof(struct volume_information); ++ case AT_INDEX_ROOT: ++ return sizeof(struct index_root); + case AT_EA_INFORMATION: + return sizeof(struct ea_information); + default: +@@ -615,6 +617,31 @@ static bool ntfs_volume_name_attr_value_ + return value_length <= NTFS_MAX_LABEL_LEN * sizeof(__le16); + } + ++static bool ntfs_index_root_attr_value_is_valid(const u8 *value, const u32 value_length) ++{ ++ const struct index_root *ir; ++ u32 index_size; ++ u32 entries_offset; ++ u32 index_length; ++ u32 allocated_size; ++ ++ ir = (const struct index_root *)value; ++ index_size = value_length - offsetof(struct index_root, index); ++ entries_offset = le32_to_cpu(ir->index.entries_offset); ++ index_length = le32_to_cpu(ir->index.index_length); ++ allocated_size = le32_to_cpu(ir->index.allocated_size); ++ ++ if ((entries_offset | index_length | allocated_size) & 7 || ++ entries_offset < sizeof(struct index_header) || ++ entries_offset > index_length || ++ index_length > allocated_size || ++ allocated_size > index_size || ++ index_length - entries_offset < sizeof(struct index_entry_header)) ++ return false; ++ ++ return true; ++} ++ + struct ntfs_resident_attr_value { + const u8 *data; + u32 len; +@@ -688,6 +715,10 @@ static bool ntfs_attr_value_is_valid(str + if (!ntfs_volume_name_attr_value_is_valid(value.len)) + goto corrupt; + break; ++ case AT_INDEX_ROOT: ++ if (!ntfs_index_root_attr_value_is_valid(value.data, value.len)) ++ goto corrupt; ++ break; + } + return true; + diff --git a/queue-7.1/ntfs-validate-resident-volume-name-values-on-lookup.patch b/queue-7.1/ntfs-validate-resident-volume-name-values-on-lookup.patch new file mode 100644 index 0000000000..246abd9703 --- /dev/null +++ b/queue-7.1/ntfs-validate-resident-volume-name-values-on-lookup.patch @@ -0,0 +1,69 @@ +From b3f6cd1d54aa279cc4f47aa27939ebe517a2c390 Mon Sep 17 00:00:00 2001 +From: DaeMyung Kang +Date: Sat, 30 May 2026 23:35:12 +0900 +Subject: ntfs: validate resident volume name values on lookup + +From: DaeMyung Kang + +commit b3f6cd1d54aa279cc4f47aa27939ebe517a2c390 upstream. + +The shared lookup-time attribute validator now has a safe caller path for +$VOLUME_NAME corruption: ntfs_write_volume_label() no longer treats +lookup errors as an absent label, and the mount path reinitializes its +search context before continuing to $VOLUME_INFORMATION. + +Add $VOLUME_NAME-specific resident value validation. A volume name is +stored as a UTF-16LE string, so reject odd byte lengths, and reject +values longer than the NTFS volume label limit. Empty labels remain +valid. + +Also reject non-resident $VOLUME_NAME records. $VOLUME_NAME is required +to be resident, like $FILE_NAME; a crafted non-resident record would +otherwise pass lookup and ntfs_write_volume_label() would remove it as if +it were a normal resident attribute. + +Cc: stable@vger.kernel.org # v7.1 +Signed-off-by: DaeMyung Kang +Signed-off-by: Namjae Jeon +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs/attrib.c | 14 +++++++++++++- + 1 file changed, 13 insertions(+), 1 deletion(-) + +--- a/fs/ntfs/attrib.c ++++ b/fs/ntfs/attrib.c +@@ -607,6 +607,14 @@ static bool ntfs_file_name_attr_value_is + value_length - offsetof(struct file_name_attr, file_name); + } + ++static bool ntfs_volume_name_attr_value_is_valid(const u32 value_length) ++{ ++ if (value_length & 1) ++ return false; ++ ++ return value_length <= NTFS_MAX_LABEL_LEN * sizeof(__le16); ++} ++ + struct ntfs_resident_attr_value { + const u8 *data; + u32 len; +@@ -657,7 +665,7 @@ static bool ntfs_attr_value_is_valid(str + u32 min_len; + + if (a->non_resident) { +- if (a->type == AT_FILE_NAME) ++ if (a->type == AT_FILE_NAME || a->type == AT_VOLUME_NAME) + goto corrupt; + if (!ntfs_non_resident_attr_value_is_valid(a)) + goto corrupt; +@@ -676,6 +684,10 @@ static bool ntfs_attr_value_is_valid(str + if (!ntfs_file_name_attr_value_is_valid(value.data, value.len)) + goto corrupt; + break; ++ case AT_VOLUME_NAME: ++ if (!ntfs_volume_name_attr_value_is_valid(value.len)) ++ goto corrupt; ++ break; + } + return true; + diff --git a/queue-7.1/ntfs3-bound-to_move-in-indx_insert_into_root-before-hdr_insert_head.patch b/queue-7.1/ntfs3-bound-to_move-in-indx_insert_into_root-before-hdr_insert_head.patch new file mode 100644 index 0000000000..acf4b65cdd --- /dev/null +++ b/queue-7.1/ntfs3-bound-to_move-in-indx_insert_into_root-before-hdr_insert_head.patch @@ -0,0 +1,82 @@ +From 9b6926ac9c970ae0b2c2fe6289b16e9aa10b6a67 Mon Sep 17 00:00:00 2001 +From: Michael Bommarito +Date: Fri, 17 Apr 2026 19:33:05 -0400 +Subject: ntfs3: bound to_move in indx_insert_into_root before hdr_insert_head + +From: Michael Bommarito + +commit 9b6926ac9c970ae0b2c2fe6289b16e9aa10b6a67 upstream. + +indx_insert_into_root() promotes a full resident $INDEX_ROOT into +$INDEX_ALLOCATION and copies all non-last resident root entries into +a newly allocated INDEX_BUFFER via hdr_insert_head(). The source +byte count 'to_move' is summed from the on-disk resident entry sizes +and is independent of the destination buffer size, which comes from +root->index_block_size (via indx->index_bits). + +A crafted NTFS image that keeps a valid, full resident root but +shrinks root->index_block_size down to 512 after the root has been +populated makes hdr_insert_head() memcpy attacker-controlled resident +entry bytes past the end of the kmalloc(1u << indx->index_bits) +allocation returned by indx_new(). For a 512-byte destination and a +resident root whose non-last entries total 560 bytes, the memcpy +overruns by 120 bytes and a following memmove extends the highest +written offset to 136 bytes past the allocation. The overflow bytes +are a direct copy of on-disk entries (via kmemdup), so they are +fully attacker-controlled. + +The write is reachable from unprivileged open(O_CREAT) on a mounted +crafted NTFS image: a single sufficiently long create in a directory +whose resident root is already full forces root promotion and +triggers the copy. + +This is a controlled out-of-bounds write of 120-136 bytes past a +kmalloc(index_block_size) allocation, with attacker-controlled +content. It is a bounded adjacent-heap corruption primitive; it is +not an arbitrary-address write. Successful exploitation into a named +victim object depends on the surrounding slab layout. + +Reject the copy at the sink. The destination's INDEX_HDR already +reports hdr_total (the payload capacity of the new buffer) and +hdr_used (the bytes already consumed by the terminal END entry +installed by indx_new()); require that to_move fits in the remaining +payload before calling hdr_insert_head(). On mismatch, fail with +-EINVAL and mark the filesystem as having a detected on-disk +inconsistency, which is the same behaviour as the surrounding +validation in this function. + +Fixes: 82cae269cfa9 ("fs/ntfs3: Add initialization of super block") +Cc: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-7 +Signed-off-by: Michael Bommarito +Signed-off-by: Konstantin Komarov +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs3/index.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +--- a/fs/ntfs3/index.c ++++ b/fs/ntfs3/index.c +@@ -1742,6 +1742,22 @@ static int indx_insert_into_root(struct + hdr_used = le32_to_cpu(hdr->used); + hdr_total = le32_to_cpu(hdr->total); + ++ /* ++ * The destination INDEX_BUFFER has 'hdr_total' bytes of payload ++ * available after the header, of which 'hdr_used' are already ++ * consumed by the single terminal END entry installed by ++ * indx_new(). A crafted image can present a resident root whose ++ * non-last entries (summing to 'to_move') exceed what fits in ++ * this buffer; copying them unchecked would overrun the ++ * kmalloc(1u << indx->index_bits) allocation backing the new ++ * buffer. Reject the copy in that case. ++ */ ++ if (to_move > hdr_total - hdr_used) { ++ err = -EINVAL; ++ ntfs_set_state(sbi, NTFS_DIRTY_ERROR); ++ goto out_put_n; ++ } ++ + /* Copy root entries into new buffer. */ + hdr_insert_head(hdr, re, to_move); + diff --git a/queue-7.1/ntfs3-cap-restart_table-free-chain-walker-at-rt-used.patch b/queue-7.1/ntfs3-cap-restart_table-free-chain-walker-at-rt-used.patch new file mode 100644 index 0000000000..afc401674e --- /dev/null +++ b/queue-7.1/ntfs3-cap-restart_table-free-chain-walker-at-rt-used.patch @@ -0,0 +1,88 @@ +From 9611f644302c07d21bc8af97e3e06a3d30064253 Mon Sep 17 00:00:00 2001 +From: Michael Bommarito +Date: Sun, 17 May 2026 19:41:40 -0400 +Subject: ntfs3: cap RESTART_TABLE free-chain walker at rt->used + +From: Michael Bommarito + +commit 9611f644302c07d21bc8af97e3e06a3d30064253 upstream. + +A crafted NTFS3 disk image triggers an in-kernel infinite loop at +mount time, hanging the mounting thread and firing the soft-lockup +watchdog within ~22s on multi-CPU hosts (panic with +kernel.softlockup_panic=1). The bug is reachable from desktop USB +auto-mount on distributions where udisks2 routes the NTFS signature +to the in-tree ntfs3 driver (Arch family and an increasing fraction +of Fedora / openSUSE / RHEL deployments); CAP_SYS_ADMIN-class manual +mount elsewhere. + +check_rstbl()'s second walker iterates the free-entry singly-linked +list headed by rt->first_free with no upper bound on iteration count: + + for (off = ff; off;) { + if (off == RESTART_ENTRY_ALLOCATED) + return false; + off = le32_to_cpu(*(__le32 *)Add2Ptr(rt, off)); + if (off > ts - sizeof(__le32)) + return false; + } + +The existing guards cover three exits: end-of-list (off == 0), the +in-use marker (off == RESTART_ENTRY_ALLOCATED), and out-of-bounds +(off > ts - sizeof(__le32)). None of the three prevents an +in-bounds cycle. + +A crafted on-disk RESTART_TABLE whose free chain contains a +self-loop or A->B->A cycle whose offsets satisfy: + + - in range [sizeof(struct RESTART_TABLE), ts - sizeof(__le32)] + - (off - sizeof(struct RESTART_TABLE)) % rsize == 0 + +passes all existing guards and spins the mount-time thread forever. +Reproduced in UML by hand-forging a 2 MB NTFS3 image whose journal +RESTART_TABLE first_free = 0x18 and whose entry at offset 0x18 +stores 0x18 as its next pointer; mount of the forged image with +the in-tree ntfs3 driver never returns. + +Bound the walker by rt->used. Each entry on a legitimate free +chain is unique, and the total slot count is ne = le16_to_cpu +(rt->used). A traversal that visits more than ne slots is by +construction malformed; reject it as a corrupt RESTART_TABLE. + +After this patch, mount of the forged image returns with -EINVAL +and a log_replay failure message, and mkntfs-produced legitimate +images mount cleanly (verified in the same UML harness). + +Fixes: b46acd6a6a62 ("fs/ntfs3: Add NTFS journal") +Cc: stable@vger.kernel.org +Signed-off-by: Michael Bommarito +Assisted-by: Claude:claude-opus-4-7 +Signed-off-by: Konstantin Komarov +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs3/fslog.c | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +--- a/fs/ntfs3/fslog.c ++++ b/fs/ntfs3/fslog.c +@@ -764,8 +764,19 @@ static bool check_rstbl(const struct RES + /* + * Walk through the list headed by the first entry to make + * sure none of the entries are currently being used. ++ * ++ * Bound traversal by ne (rt->used) to defeat a crafted on-disk ++ * cycle in the free chain. Each entry in a legitimate free ++ * list is unique, so a chain that visits more than ne slots ++ * is malformed. Without this guard, an attacker-controlled ++ * RESTART_TABLE with a self-loop or A->B->A cycle whose ++ * offsets satisfy the existing alignment + in-bounds guards ++ * spins forever at mount time. + */ +- for (off = ff; off;) { ++ for (off = ff, i = 0; off; i++) { ++ if (i > ne) ++ return false; ++ + if (off == RESTART_ENTRY_ALLOCATED) + return false; + diff --git a/queue-7.1/ntfs3-fix-out-of-bounds-read-in-decompress_lznt.patch b/queue-7.1/ntfs3-fix-out-of-bounds-read-in-decompress_lznt.patch new file mode 100644 index 0000000000..63eb5e9ee4 --- /dev/null +++ b/queue-7.1/ntfs3-fix-out-of-bounds-read-in-decompress_lznt.patch @@ -0,0 +1,35 @@ +From 7160a57192fb16d7a6fa9b7f5c7ac341d2444a89 Mon Sep 17 00:00:00 2001 +From: Tristan Madani +Date: Sat, 18 Apr 2026 13:11:18 +0000 +Subject: ntfs3: fix out-of-bounds read in decompress_lznt + +From: Tristan Madani + +commit 7160a57192fb16d7a6fa9b7f5c7ac341d2444a89 upstream. + +decompress_lznt() does not validate array index bounds before accessing +the decompression table. A corrupted NTFS3 image with invalid compressed +data can trigger an out-of-bounds read. + +Add index bounds checking to prevent the OOB access. + +Reported-by: syzbot+39b2fb0f2638669008ec@syzkaller.appspotmail.com +Cc: stable@vger.kernel.org +Signed-off-by: Tristan Madani +Signed-off-by: Konstantin Komarov +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs3/lznt.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/fs/ntfs3/lznt.c ++++ b/fs/ntfs3/lznt.c +@@ -240,7 +240,7 @@ static inline ssize_t decompress_chunk(u + if (up - unc > LZNT_CHUNK_SIZE) + return -EINVAL; + /* Correct index */ +- while (unc + s_max_off[index] < up) ++ while (index < ARRAY_SIZE(s_max_off) - 1 && unc + s_max_off[index] < up) + index += 1; + + /* Check the current flag for zero. */ diff --git a/queue-7.1/ntfs3-validate-split-point-offset-in-indx_insert_into_buffer.patch b/queue-7.1/ntfs3-validate-split-point-offset-in-indx_insert_into_buffer.patch new file mode 100644 index 0000000000..0f3e333219 --- /dev/null +++ b/queue-7.1/ntfs3-validate-split-point-offset-in-indx_insert_into_buffer.patch @@ -0,0 +1,82 @@ +From f1df9d771df47aa40de6d70949c28720ae1e430d Mon Sep 17 00:00:00 2001 +From: Michael Bommarito +Date: Fri, 17 Apr 2026 18:57:12 -0400 +Subject: ntfs3: validate split-point offset in indx_insert_into_buffer + +From: Michael Bommarito + +commit f1df9d771df47aa40de6d70949c28720ae1e430d upstream. + +indx_insert_into_buffer() computes + + used = used1 - to_copy - sp_size; + memmove(de_t, Add2Ptr(sp, sp_size), used - le32_to_cpu(hdr1->de_off)); + +where sp and sp_size come from hdr_find_split(). hdr_find_split() +walks entries by le16_to_cpu(e->size) without validating that each +step stays within hdr->used or that the size field is at least +sizeof(struct NTFS_DE). index_hdr_check(), the on-load gatekeeper, +only validates header-level fields (used, total, de_off) and does +not walk per-entry sizes. + +A crafted NTFS image whose leaf INDEX_HDR reports used == total but +contains one interior NTFS_DE with size = 0xFFF0 therefore passes +validation, descends to indx_insert_into_buffer() through the +ntfs_create() -> indx_insert_entry() path, and makes hdr_find_split() +return an sp whose sp_size (0xFFF0) greatly exceeds the remaining +bytes in the buffer. The u32 subtraction underflows and the memmove +count becomes a near-4-GiB value, producing an out-of-bounds kernel +write that corrupts adjacent allocations and panics the kernel. + +Reproduced on 7.0.0-rc7 with UML + KASAN via a crafted image and a +single 'touch' inside the mounted directory; crash site resolves to +fs/ntfs3/index.c at the memmove. Trigger requires only local mount +of an attacker-supplied filesystem image (USB, loopback, or removable +media auto-mount). + +Reject the split whenever the chosen sp plus its declared size +already extends past hdr1->used. This is the minimal fix; it +preserves the existing hdr_find_split() contract and relies on the +same out: cleanup path as the pre-existing error returns. + +A prior OOB read in the very same indx_insert_into_buffer() memmove +was fixed in commit b8c44949044e ("fs/ntfs3: Fix OOB read in +indx_insert_into_buffer") by tightening hdr_find_e(), but that fix +does not cover the split-point size field path addressed here: sp is +returned by hdr_find_split(), not hdr_find_e(), and the underflow is +driven by sp->size rather than hdr->used exceeding hdr->total. + +Fixes: 82cae269cfa9 ("fs/ntfs3: Add initialization of super block") +Cc: stable@vger.kernel.org +Reported-by: Michael Bommarito +Assisted-by: Claude:claude-opus-4-7 +Signed-off-by: Michael Bommarito +Signed-off-by: Konstantin Komarov +Signed-off-by: Greg Kroah-Hartman +--- + fs/ntfs3/index.c | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +--- a/fs/ntfs3/index.c ++++ b/fs/ntfs3/index.c +@@ -1862,6 +1862,20 @@ indx_insert_into_buffer(struct ntfs_inde + memcpy(up_e, sp, sp_size); + + used1 = le32_to_cpu(hdr1->used); ++ ++ /* ++ * hdr_find_split does not validate per-entry sizes, so a crafted ++ * NTFS_DE whose le16 size field is out of range can place sp such ++ * that (PtrOffset(hdr1, sp) + sp_size) exceeds used1. Without this ++ * guard the u32 'used = used1 - to_copy - sp_size' underflows and ++ * the subsequent memmove count becomes a near-4-GiB value, ++ * triggering an out-of-bounds kernel write. ++ */ ++ if (PtrOffset(hdr1, sp) + sp_size > used1) { ++ err = -EINVAL; ++ goto out; ++ } ++ + hdr1_saved = kmemdup(hdr1, used1, GFP_NOFS); + if (!hdr1_saved) { + err = -ENOMEM; diff --git a/queue-7.1/ocfs2-add-journal-null-check-in-ocfs2_checkpoint_inode.patch b/queue-7.1/ocfs2-add-journal-null-check-in-ocfs2_checkpoint_inode.patch new file mode 100644 index 0000000000..ad194527f4 --- /dev/null +++ b/queue-7.1/ocfs2-add-journal-null-check-in-ocfs2_checkpoint_inode.patch @@ -0,0 +1,50 @@ +From a291c77c034b7a81849ce9b71cc9ecda9e587d89 Mon Sep 17 00:00:00 2001 +From: Joseph Qi +Date: Sun, 31 May 2026 21:16:45 +0800 +Subject: ocfs2: add journal NULL check in ocfs2_checkpoint_inode() + +From: Joseph Qi + +commit a291c77c034b7a81849ce9b71cc9ecda9e587d89 upstream. + +During unmount, ocfs2_journal_shutdown() frees the journal and sets +osb->journal to NULL. Later, when VFS evicts remaining cached inodes, +ocfs2_evict_inode() -> ocfs2_clear_inode() -> ocfs2_checkpoint_inode() +-> ocfs2_ci_fully_checkpointed() dereferences osb->journal, causing a +NULL pointer dereference. + +Fix this by adding a NULL check for osb->journal in +ocfs2_checkpoint_inode(). If the journal is NULL, it has already been +fully flushed and destroyed during shutdown, so there is nothing to +checkpoint. + +Link: https://lore.kernel.org/20260531131645.3650299-1-joseph.qi@linux.alibaba.com +Reported-by: Farhad Alemi +Fixes: da5e7c87827e ("ocfs2: cleanup journal init and shutdown") +Signed-off-by: Joseph Qi +Tested-by: Farhad Alemi +Reviewed-by: Heming Zhao +Cc: Mark Fasheh +Cc: Joel Becker +Cc: Junxiao Bi +Cc: Changwei Ge +Cc: Jun Piao +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + fs/ocfs2/journal.h | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/fs/ocfs2/journal.h ++++ b/fs/ocfs2/journal.h +@@ -196,6 +196,9 @@ static inline void ocfs2_checkpoint_inod + if (ocfs2_mount_local(osb)) + return; + ++ if (!osb->journal) ++ return; ++ + if (!ocfs2_ci_fully_checkpointed(INODE_CACHE(inode))) { + /* WARNING: This only kicks off a single + * checkpoint. If someone races you and adds more diff --git a/queue-7.1/ocfs2-avoid-moving-extents-to-occupied-clusters.patch b/queue-7.1/ocfs2-avoid-moving-extents-to-occupied-clusters.patch new file mode 100644 index 0000000000..97250eae0b --- /dev/null +++ b/queue-7.1/ocfs2-avoid-moving-extents-to-occupied-clusters.patch @@ -0,0 +1,67 @@ +From 22920541c35a9f23f219038ba5874c843a7c4419 Mon Sep 17 00:00:00 2001 +From: Kyle Zeng +Date: Thu, 11 Jun 2026 14:35:10 -0700 +Subject: ocfs2: avoid moving extents to occupied clusters + +From: Kyle Zeng + +commit 22920541c35a9f23f219038ba5874c843a7c4419 upstream. + +For non-auto OCFS2_IOC_MOVE_EXT operations, userspace supplies a physical +me_goal. ocfs2_move_extent() initializes new_phys_cpos from that goal and +expects ocfs2_probe_alloc_group() to replace it with a free run in the +target block group. + +The probe currently leaves *phys_cpos unchanged if the scan reaches the +end of the group without finding a free run. An occupied goal at the last +bit can therefore survive the probe and be passed to +__ocfs2_move_extent(), which copies file data into a cluster still owned +by another inode before the bitmap is updated. + +When the probe does find a free run, it also subtracts move_len from the +ending bit. The start of an N-bit run ending at i is i - N + 1, so the +current calculation can report the bit immediately before the free run. + +Clear *phys_cpos before scanning and use the correct free-run start. +Callers already treat a zero result as -ENOSPC, so failed probes no longer +continue with an occupied caller-controlled goal. + +Link: https://lore.kernel.org/20260611213510.16956-1-kylebot@openai.com +Fixes: e6b5859cccfa ("Ocfs2/move_extents: helper to probe a proper region to move in an alloc group.") +Fixes: 236b9254f8d1 ("ocfs2: fix non-auto defrag path not working issue") +Assisted-by: Codex:gpt-5.5 +Signed-off-by: Kyle Zeng +Reviewed-by: Joseph Qi +Cc: Mark Fasheh +Cc: Joel Becker +Cc: Junxiao Bi +Cc: Changwei Ge +Cc: Jun Piao +Cc: Heming Zhao +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + fs/ocfs2/move_extents.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +--- a/fs/ocfs2/move_extents.c ++++ b/fs/ocfs2/move_extents.c +@@ -534,6 +534,8 @@ static void ocfs2_probe_alloc_group(stru + u32 base_cpos = ocfs2_blocks_to_clusters(inode->i_sb, + le64_to_cpu(gd->bg_blkno)); + ++ *phys_cpos = 0; ++ + for (i = base_bit; i < le16_to_cpu(gd->bg_bits); i++) { + + used = ocfs2_test_bit(i, (unsigned long *)gd->bg_bitmap); +@@ -555,7 +557,7 @@ static void ocfs2_probe_alloc_group(stru + last_free_bits++; + + if (last_free_bits == move_len) { +- i -= move_len; ++ i = i - move_len + 1; + *goal_bit = i; + *phys_cpos = base_cpos + i; + break; diff --git a/queue-7.1/ocfs2-fix-null-h_transaction-deref-in-ocfs2_assure_trans_credits.patch b/queue-7.1/ocfs2-fix-null-h_transaction-deref-in-ocfs2_assure_trans_credits.patch new file mode 100644 index 0000000000..1b69dd4834 --- /dev/null +++ b/queue-7.1/ocfs2-fix-null-h_transaction-deref-in-ocfs2_assure_trans_credits.patch @@ -0,0 +1,67 @@ +From f9ab30c96b0f00c20c6dac93681bdae3a033d229 Mon Sep 17 00:00:00 2001 +From: Ian Bridges +Date: Thu, 11 Jun 2026 09:46:38 -0500 +Subject: ocfs2: fix NULL h_transaction deref in ocfs2_assure_trans_credits + +From: Ian Bridges + +commit f9ab30c96b0f00c20c6dac93681bdae3a033d229 upstream. + +[BUG] +A direct write over unwritten extents can panic the kernel in +ocfs2_assure_trans_credits() when the journal aborts during DIO +completion. The crash is a general protection fault from a NULL pointer +dereference. + +[CAUSE] +ocfs2_dio_end_io_write() loops over a direct write's unwritten extents, +marking each written under a single journal handle. If the journal +aborts (for example after an I/O error) while the extent tree is being +updated, the handle is left aborted with its transaction pointer +cleared. The extent merge treats that failure as not critical and +reports success, so the loop keeps using the handle. +ocfs2_assure_trans_credits() reads the handle's remaining credits +without first checking whether the handle is aborted, and that read +dereferences the cleared transaction pointer. + +[FIX] +A journal abort is recorded in the handle itself, so callers are +expected to test the handle rather than rely on a returned error. +Make ocfs2_assure_trans_credits() do that, as the other ocfs2 journal +helpers already do, and return -EROFS when the handle is aborted. + +Link: https://lore.kernel.org/airKTsM1fRVN-Wj7@dev +Fixes: be346c1a6eeb ("ocfs2: fix DIO failure due to insufficient transaction credits") +Signed-off-by: Ian Bridges +Reported-by: syzbot+e9c15ff790cea6a0cfae@syzkaller.appspotmail.com +Closes: https://syzkaller.appspot.com/bug?extid=e9c15ff790cea6a0cfae +Reviewed-by: Joseph Qi +Cc: Mark Fasheh +Cc: Joel Becker +Cc: Junxiao Bi +Cc: Changwei Ge +Cc: Jun Piao +Cc: Heming Zhao +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + fs/ocfs2/journal.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +--- a/fs/ocfs2/journal.c ++++ b/fs/ocfs2/journal.c +@@ -473,8 +473,12 @@ bail: + */ + int ocfs2_assure_trans_credits(handle_t *handle, int nblocks) + { +- int old_nblks = jbd2_handle_buffer_credits(handle); ++ int old_nblks; + ++ if (is_handle_aborted(handle)) ++ return -EROFS; ++ ++ old_nblks = jbd2_handle_buffer_credits(handle); + trace_ocfs2_assure_trans_credits(old_nblks); + if (old_nblks >= nblocks) + return 0; diff --git a/queue-7.1/ocfs2-fix-ubsan-array-index-out-of-bounds-in-ocfs2_sum_rightmost_rec.patch b/queue-7.1/ocfs2-fix-ubsan-array-index-out-of-bounds-in-ocfs2_sum_rightmost_rec.patch new file mode 100644 index 0000000000..b64b889cbd --- /dev/null +++ b/queue-7.1/ocfs2-fix-ubsan-array-index-out-of-bounds-in-ocfs2_sum_rightmost_rec.patch @@ -0,0 +1,87 @@ +From 452a8467be8143747292218212671deeb186d2ae Mon Sep 17 00:00:00 2001 +From: Ian Bridges +Date: Wed, 10 Jun 2026 19:23:11 -0500 +Subject: ocfs2: fix UBSAN array-index-out-of-bounds in ocfs2_sum_rightmost_rec + +From: Ian Bridges + +commit 452a8467be8143747292218212671deeb186d2ae upstream. + +[BUG] +On-disk corruption setting l_next_free_rec to 0 in an inode's embedded +extent list triggers a UBSAN panic on the next write to that file. + +[CAUSE] +ocfs2_sum_rightmost_rec() computes +i = le16_to_cpu(el->l_next_free_rec) - 1 +and accesses el->l_recs[i] without validating i. When l_next_free_rec +is 0, i becomes -1; when l_next_free_rec exceeds l_count, i falls +past the end of the array. Either case violates the +__counted_by_le(l_count) annotation on l_recs[] and triggers UBSAN. + +[FIX] +Validate the inode's embedded extent list when the inode is read, in +ocfs2_validate_inode_block(): l_count must be non-zero and no larger +than the inode block can hold, and l_next_free_rec must not exceed +l_count. A corrupt list is rejected at read time, before the b-tree +code can index l_recs[] out of bounds. + +Link: https://lore.kernel.org/ain_780qc0P4ypNd@dev +Signed-off-by: Ian Bridges +Reported-by: syzbot+be16e33db01e6644db7a@syzkaller.appspotmail.com +Closes: https://syzkaller.appspot.com/bug?extid=be16e33db01e6644db7a +Reviewed-by: Joseph Qi +Cc: Mark Fasheh +Cc: Joel Becker +Cc: Junxiao Bi +Cc: Changwei Ge +Cc: Jun Piao +Cc: Heming Zhao +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + fs/ocfs2/inode.c | 32 ++++++++++++++++++++++++++++++++ + 1 file changed, 32 insertions(+) + +--- a/fs/ocfs2/inode.c ++++ b/fs/ocfs2/inode.c +@@ -1582,6 +1582,38 @@ int ocfs2_validate_inode_block(struct su + goto bail; + } + ++ if (ocfs2_dinode_has_extents(di)) { ++ struct ocfs2_extent_list *el = &di->id2.i_list; ++ u16 count = le16_to_cpu(el->l_count); ++ u16 next_free = le16_to_cpu(el->l_next_free_rec); ++ ++ if (count == 0) { ++ rc = ocfs2_error(sb, ++ "Invalid dinode %llu: extent list l_count is zero\n", ++ (unsigned long long)bh->b_blocknr); ++ goto bail; ++ } ++ /* ++ * The exact capacity depends on i_xattr_inline_size, another ++ * unvalidated on-disk field. Inline xattrs only shrink the ++ * list, so the no-xattr maximum is a safe upper bound that a ++ * valid l_count never exceeds. ++ */ ++ if (count > ocfs2_extent_recs_per_inode(sb)) { ++ rc = ocfs2_error(sb, ++ "Invalid dinode %llu: extent list l_count %u exceeds max %u\n", ++ (unsigned long long)bh->b_blocknr, count, ++ ocfs2_extent_recs_per_inode(sb)); ++ goto bail; ++ } ++ if (next_free > count) { ++ rc = ocfs2_error(sb, ++ "Invalid dinode %llu: extent list l_next_free_rec %u exceeds l_count %u\n", ++ (unsigned long long)bh->b_blocknr, next_free, count); ++ goto bail; ++ } ++ } ++ + rc = 0; + + bail: diff --git a/queue-7.1/ocfs2-reject-dinodes-whose-i_rdev-disagrees-with-the-file-type.patch b/queue-7.1/ocfs2-reject-dinodes-whose-i_rdev-disagrees-with-the-file-type.patch new file mode 100644 index 0000000000..e3d50e38da --- /dev/null +++ b/queue-7.1/ocfs2-reject-dinodes-whose-i_rdev-disagrees-with-the-file-type.patch @@ -0,0 +1,120 @@ +From 51407c2d249987a466416890a5c931a2354a46aa Mon Sep 17 00:00:00 2001 +From: Michael Bommarito +Date: Tue, 19 May 2026 07:04:03 -0400 +Subject: ocfs2: reject dinodes whose i_rdev disagrees with the file type + +From: Michael Bommarito + +commit 51407c2d249987a466416890a5c931a2354a46aa upstream. + +id1.dev1.i_rdev is the device-number arm of the ocfs2_dinode id1 union. +It is only meaningful for character and block device inodes. For any +other user-visible file type the on-disk value must be zero. + +ocfs2_populate_inode() currently copies id1.dev1.i_rdev into inode->i_rdev +before the S_IFMT switch decides whether the inode is a special file. A +non-device inode with a non-zero i_rdev can therefore publish stale or +attacker-controlled device state into the in-core inode. + +System inodes legitimately use other arms of the same union, so keep the +cross-check restricted to non-system inodes. Factor that predicate into a +helper and use it in both the normal validator and online filecheck path; +filecheck reports the malformed dinode through +OCFS2_FILECHECK_ERR_INVALIDINO instead of ocfs2_error(). + +Link: https://lore.kernel.org/20260519110404.1803902-3-michael.bommarito@gmail.com +Fixes: b657c95c1108 ("ocfs2: Wrap inode block reads in a dedicated function.") +Signed-off-by: Michael Bommarito +Assisted-by: Claude:claude-opus-4-7 +Reviewed-by: Joseph Qi +Cc: Changwei Ge +Cc: Heming Zhao +Cc: Joel Becker +Cc: Jun Piao +Cc: Junxiao Bi +Cc: Mark Fasheh +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + fs/ocfs2/inode.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 55 insertions(+) + +--- a/fs/ocfs2/inode.c ++++ b/fs/ocfs2/inode.c +@@ -72,6 +72,16 @@ static bool ocfs2_valid_inode_mode(umode + return fs_umode_to_ftype(mode) != FT_UNKNOWN; + } + ++static bool ocfs2_dinode_has_unexpected_rdev(struct ocfs2_dinode *di) ++{ ++ umode_t mode = le16_to_cpu(di->i_mode); ++ ++ if (le32_to_cpu(di->i_flags) & OCFS2_SYSTEM_FL) ++ return false; ++ ++ return !S_ISCHR(mode) && !S_ISBLK(mode) && di->id1.dev1.i_rdev != 0; ++} ++ + void ocfs2_set_inode_flags(struct inode *inode) + { + unsigned int flags = OCFS2_I(inode)->ip_attr; +@@ -1518,6 +1528,41 @@ int ocfs2_validate_inode_block(struct su + goto bail; + } + ++ /* ++ * id1.dev1.i_rdev is the device-number arm of the id1 union and ++ * is only meaningful for character and block device inodes. For ++ * any other regular user-visible file type the on-disk value ++ * must be zero. ocfs2_populate_inode() currently runs ++ * ++ * inode->i_rdev = huge_decode_dev(le64_to_cpu(fe->id1.dev1.i_rdev)); ++ * ++ * unconditionally, before the S_IFMT switch decides whether the ++ * inode is a special file. As a result, an i_rdev value present ++ * on a non-device inode is silently published into the in-core ++ * inode; a subsequent forced re-read or in-core mode mutation ++ * (cluster peer with raw write access to the shared LUN, ++ * on-disk corruption, or a separately forged dinode) can then ++ * expose the attacker-controlled device number to ++ * init_special_inode() without ever showing an unusual i_mode ++ * at validation time. ++ * ++ * System inodes (OCFS2_SYSTEM_FL) legitimately use the bitmap1 ++ * and journal1 arms of the same union (allocator i_used / ++ * i_total counters and the journal ij_flags / ++ * ij_recovery_generation pair); those bytes are not an i_rdev ++ * and must not be checked here. Restrict the cross-check to ++ * non-system inodes, which is the full attacker-controllable ++ * surface. ++ */ ++ if (ocfs2_dinode_has_unexpected_rdev(di)) { ++ rc = ocfs2_error(sb, ++ "Invalid dinode #%llu: non-device mode 0%o with i_rdev %llu\n", ++ (unsigned long long)bh->b_blocknr, ++ le16_to_cpu(di->i_mode), ++ (unsigned long long)le64_to_cpu(di->id1.dev1.i_rdev)); ++ goto bail; ++ } ++ + if (le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_DATA_FL) { + struct ocfs2_inline_data *data = &di->id2.i_data; + +@@ -1712,6 +1757,16 @@ static int ocfs2_filecheck_validate_inod + (unsigned long long)bh->b_blocknr, + le16_to_cpu(di->i_mode)); + rc = -OCFS2_FILECHECK_ERR_INVALIDINO; ++ goto bail; ++ } ++ ++ if (ocfs2_dinode_has_unexpected_rdev(di)) { ++ mlog(ML_ERROR, ++ "Filecheck: invalid dinode #%llu: non-device mode 0%o with i_rdev %llu\n", ++ (unsigned long long)bh->b_blocknr, ++ le16_to_cpu(di->i_mode), ++ (unsigned long long)le64_to_cpu(di->id1.dev1.i_rdev)); ++ rc = -OCFS2_FILECHECK_ERR_INVALIDINO; + } + + bail: diff --git a/queue-7.1/ocfs2-reject-dinodes-with-non-canonical-i_mode-type.patch b/queue-7.1/ocfs2-reject-dinodes-with-non-canonical-i_mode-type.patch new file mode 100644 index 0000000000..90c813522d --- /dev/null +++ b/queue-7.1/ocfs2-reject-dinodes-with-non-canonical-i_mode-type.patch @@ -0,0 +1,141 @@ +From 5366a017099c6a3c443be908a05f26fd72af12a1 Mon Sep 17 00:00:00 2001 +From: Michael Bommarito +Date: Tue, 19 May 2026 07:04:02 -0400 +Subject: ocfs2: reject dinodes with non-canonical i_mode type + +From: Michael Bommarito + +commit 5366a017099c6a3c443be908a05f26fd72af12a1 upstream. + +Patch series "ocfs2: harden inode validators against forged metadata", v2. + +This series adds three structural checks to OCFS2 dinode validation so +malformed on-disk fields are rejected before ocfs2_populate_inode() copies +them into the in-core inode. + +The checks cover: + + - i_mode values whose type bits do not name a canonical POSIX file + type; + - non-device dinodes whose id1.dev1.i_rdev field is non-zero; and + - non-inline dinodes that claim non-zero i_size while i_clusters is + zero, covering directories unconditionally and regular files on + non-sparse volumes. + +The normal read path reports these through ocfs2_error(), matching the +existing suballoc-slot, inline-data, chain-list, and refcount checks. The +online filecheck path uses the same structural predicates but keeps its +own reporting contract, returning OCFS2_FILECHECK_ERR_INVALIDINO instead +of calling ocfs2_error(). + + +This patch (of 3): + +ocfs2_validate_inode_block() currently accepts any non-zero i_mode value. +ocfs2_populate_inode() then copies that mode verbatim into inode->i_mode +and dispatches on i_mode & S_IFMT to the file/dir/symlink/special_file +iops; an unrecognised type falls through to ocfs2_special_file_iops and +init_special_inode(). + +Reject dinodes whose type bits do not name one of the seven canonical +POSIX file types. Use fs_umode_to_ftype(), the same generic file-type +conversion helper OCFS2 already uses for directory entries, so the +accepted inode type set matches the kernel file-type vocabulary instead of +open-coding a local switch. + +Apply the same structural check to the online filecheck read path. +filecheck keeps its own error namespace, so it reports malformed i_mode +through the filecheck logger and OCFS2_FILECHECK_ERR_INVALIDINO instead of +calling ocfs2_error(), but it must not allow a malformed dinode to proceed +into ocfs2_populate_inode(). + +Link: https://lore.kernel.org/20260519110404.1803902-1-michael.bommarito@gmail.com +Link: https://lore.kernel.org/20260519110404.1803902-2-michael.bommarito@gmail.com +Fixes: b657c95c1108 ("ocfs2: Wrap inode block reads in a dedicated function.") +Signed-off-by: Michael Bommarito +Link: https://sashiko.dev/#/patchset/20260517111015.3187935-1-michael.bommarito%40gmail.com +Assisted-by: Claude:claude-opus-4-7 +Reviewed-by: Joseph Qi +Cc: Changwei Ge +Cc: Heming Zhao +Cc: Joel Becker +Cc: Jun Piao +Cc: Junxiao Bi +Cc: Mark Fasheh +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + fs/ocfs2/inode.c | 36 ++++++++++++++++++++++++++++++++++-- + 1 file changed, 34 insertions(+), 2 deletions(-) + +--- a/fs/ocfs2/inode.c ++++ b/fs/ocfs2/inode.c +@@ -13,6 +13,7 @@ + #include + #include + #include ++#include + + #include + +@@ -64,7 +65,12 @@ static int ocfs2_filecheck_read_inode_bl + static int ocfs2_filecheck_validate_inode_block(struct super_block *sb, + struct buffer_head *bh); + static int ocfs2_filecheck_repair_inode_block(struct super_block *sb, +- struct buffer_head *bh); ++ struct buffer_head *bh); ++ ++static bool ocfs2_valid_inode_mode(umode_t mode) ++{ ++ return fs_umode_to_ftype(mode) != FT_UNKNOWN; ++} + + void ocfs2_set_inode_flags(struct inode *inode) + { +@@ -1494,6 +1500,24 @@ int ocfs2_validate_inode_block(struct su + goto bail; + } + ++ /* ++ * Reject dinodes whose i_mode does not name one of the seven ++ * canonical POSIX file types. ocfs2_populate_inode() copies ++ * i_mode verbatim into inode->i_mode and then dispatches via ++ * switch (mode & S_IFMT) to file/dir/symlink/special_file iops; ++ * an unrecognised type falls into ocfs2_special_file_iops with ++ * init_special_inode(), which interprets i_rdev. Constrain the ++ * type here so the dispatch only ever sees a value mkfs.ocfs2 / ++ * VFS can produce. ++ */ ++ if (!ocfs2_valid_inode_mode(le16_to_cpu(di->i_mode))) { ++ rc = ocfs2_error(sb, ++ "Invalid dinode #%llu: mode 0%o has unknown file type\n", ++ (unsigned long long)bh->b_blocknr, ++ le16_to_cpu(di->i_mode)); ++ goto bail; ++ } ++ + if (le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_DATA_FL) { + struct ocfs2_inline_data *data = &di->id2.i_data; + +@@ -1679,6 +1703,15 @@ static int ocfs2_filecheck_validate_inod + (unsigned long long)bh->b_blocknr, + le32_to_cpu(di->i_fs_generation)); + rc = -OCFS2_FILECHECK_ERR_GENERATION; ++ goto bail; ++ } ++ ++ if (!ocfs2_valid_inode_mode(le16_to_cpu(di->i_mode))) { ++ mlog(ML_ERROR, ++ "Filecheck: invalid dinode #%llu: mode 0%o has unknown file type\n", ++ (unsigned long long)bh->b_blocknr, ++ le16_to_cpu(di->i_mode)); ++ rc = -OCFS2_FILECHECK_ERR_INVALIDINO; + } + + bail: +@@ -1867,4 +1900,3 @@ const struct ocfs2_caching_operations oc + .co_io_lock = ocfs2_inode_cache_io_lock, + .co_io_unlock = ocfs2_inode_cache_io_unlock, + }; +- diff --git a/queue-7.1/ocfs2-reject-non-inline-dinodes-with-i_size-and-zero-i_clusters.patch b/queue-7.1/ocfs2-reject-non-inline-dinodes-with-i_size-and-zero-i_clusters.patch new file mode 100644 index 0000000000..fd1cdf5aec --- /dev/null +++ b/queue-7.1/ocfs2-reject-non-inline-dinodes-with-i_size-and-zero-i_clusters.patch @@ -0,0 +1,135 @@ +From 7ebc672fab7a76e1e47e0f2fc1ee48118d27fde4 Mon Sep 17 00:00:00 2001 +From: Michael Bommarito +Date: Tue, 19 May 2026 07:04:04 -0400 +Subject: ocfs2: reject non-inline dinodes with i_size and zero i_clusters + +From: Michael Bommarito + +commit 7ebc672fab7a76e1e47e0f2fc1ee48118d27fde4 upstream. + +On a volume mounted without OCFS2_FEATURE_INCOMPAT_SPARSE_ALLOC, a +non-inline regular file with non-zero i_size and zero i_clusters is +structurally malformed: the extent map declares no allocated clusters yet +the size header claims content exists. Keep rejecting that shape, but +express it through a shared predicate so the same invariant is available +to normal inode reads and online filecheck. + +The same zero-cluster shape is also malformed for non-inline directories. +ocfs2 directory growth allocates backing storage before advancing i_size, +and ocfs2_dir_foreach_blk_el() later walks until ctx->pos reaches +i_size_read(inode). A forged directory dinode with a huge i_size and no +clusters would repeatedly fail on holes while advancing through the +claimed size. + +Sparse regular files remain exempt: on sparse-alloc volumes, truncate can +legitimately grow i_size without allocating clusters. System inodes and +inline-data dinodes also retain their separate storage rules. + +Mirror the check in ocfs2_filecheck_validate_inode_block() as well. +filecheck reports through its own error namespace, so malformed +size/cluster state is logged as a filecheck invalid-inode result rather +than via ocfs2_error(), but it must not proceed into +ocfs2_populate_inode(). + +Link: https://lore.kernel.org/20260519110404.1803902-4-michael.bommarito@gmail.com +Fixes: b657c95c1108 ("ocfs2: Wrap inode block reads in a dedicated function.") +Signed-off-by: Michael Bommarito +Link: https://sashiko.dev/#/patchset/20260517111015.3187935-1-michael.bommarito%40gmail.com +Assisted-by: Claude:claude-opus-4-7 +Reviewed-by: Joseph Qi +Cc: Mark Fasheh +Cc: Joel Becker +Cc: Junxiao Bi +Cc: Changwei Ge +Cc: Jun Piao +Cc: Heming Zhao +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + fs/ocfs2/inode.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 60 insertions(+) + +--- a/fs/ocfs2/inode.c ++++ b/fs/ocfs2/inode.c +@@ -82,6 +82,24 @@ static bool ocfs2_dinode_has_unexpected_ + return !S_ISCHR(mode) && !S_ISBLK(mode) && di->id1.dev1.i_rdev != 0; + } + ++static bool ocfs2_dinode_has_size_without_clusters(struct super_block *sb, ++ struct ocfs2_dinode *di) ++{ ++ umode_t mode = le16_to_cpu(di->i_mode); ++ ++ if (le32_to_cpu(di->i_flags) & OCFS2_SYSTEM_FL) ++ return false; ++ if (le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_DATA_FL) ++ return false; ++ if (!le64_to_cpu(di->i_size) || le32_to_cpu(di->i_clusters)) ++ return false; ++ ++ if (S_ISDIR(mode)) ++ return true; ++ ++ return !ocfs2_sparse_alloc(OCFS2_SB(sb)) && S_ISREG(mode); ++} ++ + void ocfs2_set_inode_flags(struct inode *inode) + { + unsigned int flags = OCFS2_I(inode)->ip_attr; +@@ -1563,6 +1581,33 @@ int ocfs2_validate_inode_block(struct su + goto bail; + } + ++ /* ++ * Non-inline directories must not have i_size without allocated ++ * clusters: directory growth adds storage before advancing i_size, ++ * and readdir walks i_size block-by-block. A forged directory ++ * with zero clusters and a huge i_size would repeatedly fault on ++ * holes while advancing through the claimed size. ++ * ++ * Non-inline regular files have the same invariant on non-sparse ++ * volumes. Sparse regular files are different: truncate can ++ * legitimately grow i_size without allocating clusters, so keep ++ * the sparse-alloc carveout for S_IFREG only. System inodes and ++ * inline-data dinodes have their own storage rules. ++ */ ++ if (ocfs2_dinode_has_size_without_clusters(sb, di)) { ++ if (S_ISDIR(le16_to_cpu(di->i_mode))) ++ rc = ocfs2_error(sb, ++ "Invalid dinode #%llu: directory i_size %llu with i_clusters 0 and no inline-data flag\n", ++ (unsigned long long)bh->b_blocknr, ++ (unsigned long long)le64_to_cpu(di->i_size)); ++ else ++ rc = ocfs2_error(sb, ++ "Invalid dinode #%llu: regular file i_size %llu with i_clusters 0 and no inline-data flag on non-sparse volume\n", ++ (unsigned long long)bh->b_blocknr, ++ (unsigned long long)le64_to_cpu(di->i_size)); ++ goto bail; ++ } ++ + if (le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_DATA_FL) { + struct ocfs2_inline_data *data = &di->id2.i_data; + +@@ -1767,6 +1812,21 @@ static int ocfs2_filecheck_validate_inod + le16_to_cpu(di->i_mode), + (unsigned long long)le64_to_cpu(di->id1.dev1.i_rdev)); + rc = -OCFS2_FILECHECK_ERR_INVALIDINO; ++ goto bail; ++ } ++ ++ if (ocfs2_dinode_has_size_without_clusters(sb, di)) { ++ if (S_ISDIR(le16_to_cpu(di->i_mode))) ++ mlog(ML_ERROR, ++ "Filecheck: invalid dinode #%llu: directory i_size %llu with i_clusters 0 and no inline-data flag\n", ++ (unsigned long long)bh->b_blocknr, ++ (unsigned long long)le64_to_cpu(di->i_size)); ++ else ++ mlog(ML_ERROR, ++ "Filecheck: invalid dinode #%llu: regular file i_size %llu with i_clusters 0 and no inline-data flag on non-sparse volume\n", ++ (unsigned long long)bh->b_blocknr, ++ (unsigned long long)le64_to_cpu(di->i_size)); ++ rc = -OCFS2_FILECHECK_ERR_INVALIDINO; + } + + bail: diff --git a/queue-7.1/ocfs2-use-kzalloc-for-quota-recovery-bitmap-allocation.patch b/queue-7.1/ocfs2-use-kzalloc-for-quota-recovery-bitmap-allocation.patch new file mode 100644 index 0000000000..5a19dd21ae --- /dev/null +++ b/queue-7.1/ocfs2-use-kzalloc-for-quota-recovery-bitmap-allocation.patch @@ -0,0 +1,43 @@ +From 93c8c6ea90be9e9df8fe14048ad4e3caad0770a6 Mon Sep 17 00:00:00 2001 +From: Tristan Madani +Date: Sat, 18 Apr 2026 13:10:48 +0000 +Subject: ocfs2: use kzalloc for quota recovery bitmap allocation + +From: Tristan Madani + +commit 93c8c6ea90be9e9df8fe14048ad4e3caad0770a6 upstream. + +ocfs2 quota recovery allocates a bitmap buffer with kmalloc and does not +fully initialize it. This can lead to use of uninitialized bits during +quota recovery from a corrupted filesystem image. + +Use kzalloc instead to ensure the bitmap is zero-initialized. + +Link: https://lore.kernel.org/20260418131048.1052507-1-tristmd@gmail.com +Reported-by: syzbot+7ea0b96c4ddb49fd1a70@syzkaller.appspotmail.com +Signed-off-by: Tristan Madani +Reviewed-by: Joseph Qi +Cc: Mark Fasheh +Cc: Joel Becker +Cc: Junxiao Bi +Cc: Changwei Ge +Cc: Jun Piao +Cc: Heming Zhao +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + fs/ocfs2/quota_local.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/fs/ocfs2/quota_local.c ++++ b/fs/ocfs2/quota_local.c +@@ -302,7 +302,7 @@ static int ocfs2_add_recovery_chunk(stru + if (!rc) + return -ENOMEM; + rc->rc_chunk = chunk; +- rc->rc_bitmap = kmalloc(sb->s_blocksize, GFP_NOFS); ++ rc->rc_bitmap = kzalloc(sb->s_blocksize, GFP_NOFS); + if (!rc->rc_bitmap) { + kfree(rc); + return -ENOMEM; diff --git a/queue-7.1/openrisc-add-full-instruction-cache-invalidate-functions.patch b/queue-7.1/openrisc-add-full-instruction-cache-invalidate-functions.patch new file mode 100644 index 0000000000..11614f09ed --- /dev/null +++ b/queue-7.1/openrisc-add-full-instruction-cache-invalidate-functions.patch @@ -0,0 +1,105 @@ +From 1be031de802ba486a7605b52eda825f128b026e2 Mon Sep 17 00:00:00 2001 +From: Stafford Horne +Date: Fri, 22 May 2026 16:56:03 +0100 +Subject: openrisc: Add full instruction cache invalidate functions + +From: Stafford Horne + +commit 1be031de802ba486a7605b52eda825f128b026e2 upstream. + +Add functions to invalidate all cache lines which we will use for +static_key patching. + +On OpenRISC there is no instruction to invalidate an entire cache so we +loop and invalidate cache lines one by one. This is not extremely +expensive on OpenRISC as we usually have only a few hundred cache lines. + +I considered using the invalidate cache page or range functions. +However, tracking which ranges need invalidation would have been more +expensive than flushing all pages. + +Cc: stable@vger.kernel.org +Signed-off-by: Stafford Horne +Signed-off-by: Greg Kroah-Hartman +--- + arch/openrisc/include/asm/cacheflush.h | 4 ++++ + arch/openrisc/kernel/smp.c | 21 +++++++++++++++++++++ + arch/openrisc/mm/cache.c | 16 ++++++++++++++++ + 3 files changed, 41 insertions(+) + +--- a/arch/openrisc/include/asm/cacheflush.h ++++ b/arch/openrisc/include/asm/cacheflush.h +@@ -26,6 +26,7 @@ extern void local_icache_page_inv(struct + extern void local_dcache_range_flush(unsigned long start, unsigned long end); + extern void local_dcache_range_inv(unsigned long start, unsigned long end); + extern void local_icache_range_inv(unsigned long start, unsigned long end); ++extern void local_icache_all_inv(void); + + /* + * Data cache flushing always happen on the local cpu. Instruction cache +@@ -35,10 +36,13 @@ extern void local_icache_range_inv(unsig + #ifndef CONFIG_SMP + #define dcache_page_flush(page) local_dcache_page_flush(page) + #define icache_page_inv(page) local_icache_page_inv(page) ++#define icache_all_inv() local_icache_all_inv() + #else /* CONFIG_SMP */ + #define dcache_page_flush(page) local_dcache_page_flush(page) + #define icache_page_inv(page) smp_icache_page_inv(page) ++#define icache_all_inv() smp_icache_all_inv() + extern void smp_icache_page_inv(struct page *page); ++extern void smp_icache_all_inv(void); + #endif /* CONFIG_SMP */ + + /* +--- a/arch/openrisc/kernel/smp.c ++++ b/arch/openrisc/kernel/smp.c +@@ -346,3 +346,24 @@ void smp_icache_page_inv(struct page *pa + on_each_cpu(ipi_icache_page_inv, page, 1); + } + EXPORT_SYMBOL(smp_icache_page_inv); ++ ++static void ipi_icache_all_inv(void *arg) ++{ ++ local_icache_all_inv(); ++} ++ ++void smp_icache_all_inv(void) ++{ ++ if (num_online_cpus() < 2) { ++ local_icache_all_inv(); ++ return; ++ } ++ ++ /* ++ * Ensure stores complete before we request remote icaches ++ * to invalidate. ++ */ ++ mb(); ++ ++ on_each_cpu(ipi_icache_all_inv, NULL, 1); ++} +--- a/arch/openrisc/mm/cache.c ++++ b/arch/openrisc/mm/cache.c +@@ -63,6 +63,22 @@ void local_icache_page_inv(struct page * + } + EXPORT_SYMBOL(local_icache_page_inv); + ++void local_icache_all_inv(void) ++{ ++ if (cpu_cache_is_present(SPR_UPR_ICP)) { ++ unsigned long iccfgr = mfspr(SPR_ICCFGR); ++ unsigned long sets = 1 << ((iccfgr & SPR_ICCFGR_NCS) >> 3); ++ unsigned long block_size = 16 << ((iccfgr & SPR_ICCFGR_CBS) >> 7); ++ unsigned long paddr = 0; ++ unsigned long end = sets * block_size; ++ ++ while (paddr < end) { ++ mtspr(SPR_ICBIR, paddr); ++ paddr += block_size; ++ } ++ } ++} ++ + void local_dcache_range_flush(unsigned long start, unsigned long end) + { + cache_loop(start, end, SPR_DCBFR, SPR_UPR_DCP); diff --git a/queue-7.1/power-supply-bq257xx-fix-vsysmin-clamping-logic.patch b/queue-7.1/power-supply-bq257xx-fix-vsysmin-clamping-logic.patch new file mode 100644 index 0000000000..656861d359 --- /dev/null +++ b/queue-7.1/power-supply-bq257xx-fix-vsysmin-clamping-logic.patch @@ -0,0 +1,51 @@ +From b6c6b9260a92dacef6edef8e93bc2767b86b1dfe Mon Sep 17 00:00:00 2001 +From: Alexey Charkov +Date: Wed, 3 Jun 2026 00:10:50 +0400 +Subject: power: supply: bq257xx: Fix VSYSMIN clamping logic + +From: Alexey Charkov + +commit b6c6b9260a92dacef6edef8e93bc2767b86b1dfe upstream. + +The minimal system voltage (VSYSMIN) is meant to protect the battery from +dangerous over-discharge. When the device tree provides a value for the +minimum design voltage of the battery, the user should not be allowed to +set a lower VSYSMIN, as that would defeat the purpose of this protection. + +Flip the clamping logic when setting VSYSMIN to ensure that battery design +voltage is respected. + +Cc: stable@vger.kernel.org +Fixes: 1cc017b7f9c7 ("power: supply: bq257xx: Add support for BQ257XX charger") +Tested-by: Chris Morgan +Signed-off-by: Alexey Charkov +Link: https://patch.msgid.link/20260603-bq25792-v7-2-d487bed276d0@flipper.net +Signed-off-by: Sebastian Reichel +Signed-off-by: Greg Kroah-Hartman +--- + drivers/power/supply/bq257xx_charger.c | 7 +++---- + 1 file changed, 3 insertions(+), 4 deletions(-) + +--- a/drivers/power/supply/bq257xx_charger.c ++++ b/drivers/power/supply/bq257xx_charger.c +@@ -128,9 +128,8 @@ static int bq25703_get_min_vsys(struct b + * @vsys: voltage value to set in uV. + * + * This function takes a requested minimum system voltage value, clamps +- * it between the minimum supported value by the charger and a user +- * defined minimum system value, and then writes the value to the +- * appropriate register. ++ * it between the user defined minimum system value and the maximum supported ++ * value by the charger, and then writes the value to the appropriate register. + * + * Return: Returns 0 on success or error if an error occurs. + */ +@@ -139,7 +138,7 @@ static int bq25703_set_min_vsys(struct b + unsigned int reg; + int vsys_min = pdata->vsys_min; + +- vsys = clamp(vsys, BQ25703_MINVSYS_MIN_UV, vsys_min); ++ vsys = clamp(vsys, vsys_min, BQ25703_MINVSYS_MAX_UV); + reg = ((vsys - BQ25703_MINVSYS_MIN_UV) / BQ25703_MINVSYS_STEP_UV); + reg = FIELD_PREP(BQ25703_MINVSYS_MASK, reg); + diff --git a/queue-7.1/power-supply-charger-manager-fix-refcount-leak-in-is_full_charged.patch b/queue-7.1/power-supply-charger-manager-fix-refcount-leak-in-is_full_charged.patch new file mode 100644 index 0000000000..88865da8a9 --- /dev/null +++ b/queue-7.1/power-supply-charger-manager-fix-refcount-leak-in-is_full_charged.patch @@ -0,0 +1,43 @@ +From 4373cfa38ead58f980362c841b0d0bdf8c4d956c Mon Sep 17 00:00:00 2001 +From: WenTao Liang +Date: Thu, 11 Jun 2026 08:53:21 +0800 +Subject: power: supply: charger-manager: fix refcount leak in is_full_charged() + +From: WenTao Liang + +commit 4373cfa38ead58f980362c841b0d0bdf8c4d956c upstream. + +In is_full_charged(), power_supply_get_by_name() is called to +obtain a reference to the fuel_gauge power supply. If the +voltage check (uV >= desc->fullbatt_uV) succeeds, the function +returns true directly without releasing the reference, leaking +the refcount. + +Fix this by setting a flag and jumping to the out label where +power_supply_put() properly drops the reference. + +Cc: stable@vger.kernel.org +Fixes: e132fc6bb89b ("power: supply: charger-manager: Make decisions focussed on battery status") +Signed-off-by: WenTao Liang +Link: https://patch.msgid.link/20260611005322.53096-1-vulab@iscas.ac.cn +Signed-off-by: Sebastian Reichel +Signed-off-by: Greg Kroah-Hartman +--- + drivers/power/supply/charger-manager.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +--- a/drivers/power/supply/charger-manager.c ++++ b/drivers/power/supply/charger-manager.c +@@ -303,8 +303,10 @@ static bool is_full_charged(struct charg + if (cm->battery_status == POWER_SUPPLY_STATUS_FULL + && desc->fullbatt_vchkdrop_uV) + uV += desc->fullbatt_vchkdrop_uV; +- if (uV >= desc->fullbatt_uV) +- return true; ++ if (uV >= desc->fullbatt_uV) { ++ is_full = true; ++ goto out; ++ } + } + } + diff --git a/queue-7.1/power-supply-cpcap-battery-fix-missing-nvmem_device_put-causing-reference-leak.patch b/queue-7.1/power-supply-cpcap-battery-fix-missing-nvmem_device_put-causing-reference-leak.patch new file mode 100644 index 0000000000..e27f4571a5 --- /dev/null +++ b/queue-7.1/power-supply-cpcap-battery-fix-missing-nvmem_device_put-causing-reference-leak.patch @@ -0,0 +1,51 @@ +From a2c14ff63e0e02e3c832385e523e9cc81301171c Mon Sep 17 00:00:00 2001 +From: Ma Ke +Date: Fri, 24 Apr 2026 09:10:13 +0800 +Subject: power: supply: cpcap-battery: Fix missing nvmem_device_put() causing reference leak +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +From: Ma Ke + +commit a2c14ff63e0e02e3c832385e523e9cc81301171c upstream. + +In cpcap_battery_detect_battery_type(), the reference to an nvmem +device obtained via nvmem_device_find() is not released with +nvmem_device_put() on the success or read-failure paths, causing a +permanent reference leak. The driver’s retry logic on subsequent +battery property reads can compound this leak, preventing the nvmem +device from ever being freed. + +Found by code review. + +Signed-off-by: Ma Ke +Cc: stable@vger.kernel.org +Fixes: fd46821e85de ("power: supply: cpcap-battery: Add battery type auto detection for mapphone devices") +Link: https://patch.msgid.link/20260424011013.879639-1-make24@iscas.ac.cn +Signed-off-by: Sebastian Reichel +Signed-off-by: Greg Kroah-Hartman +--- + drivers/power/supply/cpcap-battery.c | 11 +++++++---- + 1 file changed, 7 insertions(+), 4 deletions(-) + +--- a/drivers/power/supply/cpcap-battery.c ++++ b/drivers/power/supply/cpcap-battery.c +@@ -439,10 +439,13 @@ static void cpcap_battery_detect_battery + if (IS_ERR_OR_NULL(nvmem)) { + ddata->check_nvmem = true; + dev_info_once(ddata->dev, "Can not find battery nvmem device. Assuming generic lipo battery\n"); +- } else if (nvmem_device_read(nvmem, 2, 1, &battery_id) < 0) { +- battery_id = 0; +- ddata->check_nvmem = true; +- dev_warn(ddata->dev, "Can not read battery nvmem device. Assuming generic lipo battery\n"); ++ } else { ++ if (nvmem_device_read(nvmem, 2, 1, &battery_id) < 0) { ++ battery_id = 0; ++ ddata->check_nvmem = true; ++ dev_warn(ddata->dev, "Can not read battery nvmem device. Assuming generic lipo battery\n"); ++ } ++ nvmem_device_put(nvmem); + } + + switch (battery_id) { diff --git a/queue-7.1/power-supply-max17042-fix-of-node-reference-imbalance.patch b/queue-7.1/power-supply-max17042-fix-of-node-reference-imbalance.patch new file mode 100644 index 0000000000..09c5ff4733 --- /dev/null +++ b/queue-7.1/power-supply-max17042-fix-of-node-reference-imbalance.patch @@ -0,0 +1,39 @@ +From 68d234144b7dffd1f50b07ba74d0d6e833ef43a4 Mon Sep 17 00:00:00 2001 +From: Johan Hovold +Date: Tue, 7 Apr 2026 14:33:38 +0200 +Subject: power: supply: max17042: fix OF node reference imbalance + +From: Johan Hovold + +commit 68d234144b7dffd1f50b07ba74d0d6e833ef43a4 upstream. + +The driver reuses the OF node of the parent multi-function device but +fails to take another reference to balance the one dropped by the +platform bus code when unbinding the MFD and deregistering the child +devices. + +Fix this by using the intended helper for reusing OF nodes. + +Fixes: 0cd4f1f77ad4 ("power: supply: max17042: add platform driver variant") +Cc: stable@vger.kernel.org # 6.14 +Cc: Dzmitry Sankouski +Signed-off-by: Johan Hovold +Link: https://patch.msgid.link/20260407123338.2677375-1-johan@kernel.org +Signed-off-by: Sebastian Reichel +Signed-off-by: Greg Kroah-Hartman +--- + drivers/power/supply/max17042_battery.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +--- a/drivers/power/supply/max17042_battery.c ++++ b/drivers/power/supply/max17042_battery.c +@@ -1254,7 +1254,8 @@ static int max17042_platform_probe(struc + if (!i2c) + return -EINVAL; + +- dev->of_node = dev->parent->of_node; ++ device_set_of_node_from_dev(dev, dev->parent); ++ + id = platform_get_device_id(pdev); + irq = platform_get_irq(pdev, 0); + diff --git a/queue-7.1/powerpc-dt_cpu_ftrs-set-cpu_ftr_p11_pvr-for-power11-and-later-processors.patch b/queue-7.1/powerpc-dt_cpu_ftrs-set-cpu_ftr_p11_pvr-for-power11-and-later-processors.patch new file mode 100644 index 0000000000..231334a46a --- /dev/null +++ b/queue-7.1/powerpc-dt_cpu_ftrs-set-cpu_ftr_p11_pvr-for-power11-and-later-processors.patch @@ -0,0 +1,73 @@ +From e4de1b9cb3b5c981e4fe9bca253a7fb9161f5acd Mon Sep 17 00:00:00 2001 +From: Amit Machhiwal +Date: Sun, 14 Jun 2026 23:04:37 +0530 +Subject: powerpc/dt_cpu_ftrs: Set CPU_FTR_P11_PVR for Power11 and later processors + +From: Amit Machhiwal + +commit e4de1b9cb3b5c981e4fe9bca253a7fb9161f5acd upstream. + +When using device tree CPU features (dt-cpu-ftrs), the kernel bypasses +the traditional cputable-based CPU identification and instead derives +CPU features from the device tree's "ibm,powerpc-cpu-features" node +provided by firmware. + +However, CPU_FTR_P11_PVR is a kernel-internal feature flag used to +identify Power11 and later processors, and is not represented in the +device tree's ISA feature set. While ISA v3.1 support (indicated by +CPU_FTR_ARCH_31) is present on both Power10 and Power11, the +CPU_FTR_P11_PVR flag is specifically needed by code that must +distinguish between Power10 and Power11 processors. + +Without this flag set, code that checks for Power11 using +cpu_has_feature(CPU_FTR_P11_PVR) will incorrectly return false on +Power11+ systems using dt-cpu-ftrs, leading to incorrect behavior. + +This issue manifests specifically in powernv environments (bare-metal +or QEMU TCG with powernv machine type), where skiboot/OPAL firmware +provides the "ibm,powerpc-cpu-features" node, causing the kernel to +use dt-cpu-ftrs. The issue does not affect pseries guests, where SLOF +firmware does not provide this node, causing the kernel to fall back +to the traditional cputable path (identify_cpu) which correctly sets +CPU_FTR_P11_PVR during PVR-based CPU identification. + +In powernv TCG guests, the missing flag causes KVM code to trigger +warnings when attempting to create KVM guests, as cpu_features shows +0x000c00eb8f4fb187 (missing bit 53) instead of the correct +0x002c00eb8f4fb187 (with bit 53 set). + +Fix this by setting CPU_FTR_P11_PVR for all processors with +PVR >= PVR_POWER11 when ISA v3.1 support is detected in +cpufeatures_setup_start(). This approach ensures forward +compatibility with future processor generations. + +Fixes: 96e266e3bcd6 ("KVM: PPC: Book3S HV: Add Power11 capability support for Nested PAPR guests") +Cc: stable@vger.kernel.org # v6.13+ +Signed-off-by: Amit Machhiwal +Reviewed-by: Mukesh Kumar Chaurasiya (IBM) +Reviewed-by: Christophe Leroy (CS GROUP) +Signed-off-by: Madhavan Srinivasan +Link: https://patch.msgid.link/20260614173437.26352-1-amachhiw@linux.ibm.com +Signed-off-by: Greg Kroah-Hartman +--- + arch/powerpc/kernel/dt_cpu_ftrs.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +--- a/arch/powerpc/kernel/dt_cpu_ftrs.c ++++ b/arch/powerpc/kernel/dt_cpu_ftrs.c +@@ -704,6 +704,15 @@ static void __init cpufeatures_setup_sta + if (isa >= ISA_V3_1) { + cur_cpu_spec->cpu_features |= CPU_FTR_ARCH_31; + cur_cpu_spec->cpu_user_features2 |= PPC_FEATURE2_ARCH_3_1; ++ ++ /* ++ * CPU_FTR_P11_PVR is a kernel-internal flag to identify ++ * Power11 and later processors. While ISA v3.1 is supported ++ * by Power10+, this flag specifically indicates Power11+ ++ * for code that needs to distinguish between P10 and P11. ++ */ ++ if (PVR_VER(mfspr(SPRN_PVR)) >= PVR_POWER11) ++ cur_cpu_spec->cpu_features |= CPU_FTR_P11_PVR; + } + } + diff --git a/queue-7.1/proc-only-bump-parent-nlink-when-registering-directories.patch b/queue-7.1/proc-only-bump-parent-nlink-when-registering-directories.patch new file mode 100644 index 0000000000..334c4373f3 --- /dev/null +++ b/queue-7.1/proc-only-bump-parent-nlink-when-registering-directories.patch @@ -0,0 +1,90 @@ +From 16b02eb4b9b272c221255c20d34ccd5db53a3ed3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= +Date: Sat, 13 Jun 2026 21:10:05 +0000 +Subject: proc: only bump parent nlink when registering directories +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +From: Krzysztof Wilczyński + +commit 16b02eb4b9b272c221255c20d34ccd5db53a3ed3 upstream. + +proc_register() increments the parent directory's link count for every +entry it registers, while remove_proc_entry() and remove_proc_subtree() +decrement it only when the removed entry is a directory. Regular files +thus inflate the parent's count while they exist, and leak one link +permanently on every create and remove cycle. + +For example, /proc/bus/pci/00 with twenty-two device files and no +subdirectories reports nlink 24 instead of 2, and SR-IOV VF enable +and disable cycles, each creating and removing the VF config space +entries under /proc/bus/pci/, inflate the link count of that +directory without bound. + +Before commit e06689bf5701 ("proc: change ->nlink under +proc_subdir_lock"), the increment lived in proc_mkdir_data() and +proc_create_mount_point(), and was therefore applied only to +directories. Moving it into proc_register() to bring it under +proc_subdir_lock dropped the S_ISDIR check. + +Thus, move the nlink accounting into pde_subdir_insert() and +pde_erase(), only updating it for directories in both, so the link +count is always changed together with the directory entry itself. + +Fixes: e06689bf5701 ("proc: change ->nlink under proc_subdir_lock") +Cc: stable@vger.kernel.org # v5.5+ +Signed-off-by: Krzysztof Wilczyński +Link: https://patch.msgid.link/20260613211005.921692-1-kwilczynski@kernel.org +Signed-off-by: Christian Brauner (Amutable) +Signed-off-by: Greg Kroah-Hartman +--- + fs/proc/generic.c | 9 ++++----- + 1 file changed, 4 insertions(+), 5 deletions(-) + +--- a/fs/proc/generic.c ++++ b/fs/proc/generic.c +@@ -112,6 +112,8 @@ static bool pde_subdir_insert(struct pro + /* Add new node and rebalance tree. */ + rb_link_node(&de->subdir_node, parent, new); + rb_insert_color(&de->subdir_node, root); ++ if (S_ISDIR(de->mode)) ++ dir->nlink++; + return true; + } + +@@ -404,7 +406,6 @@ struct proc_dir_entry *proc_register(str + write_unlock(&proc_subdir_lock); + goto out_free_inum; + } +- dir->nlink++; + write_unlock(&proc_subdir_lock); + + return dp; +@@ -702,6 +703,8 @@ static void pde_erase(struct proc_dir_en + { + rb_erase(&pde->subdir_node, &parent->subdir); + RB_CLEAR_NODE(&pde->subdir_node); ++ if (S_ISDIR(pde->mode)) ++ parent->nlink--; + } + + /* +@@ -727,8 +730,6 @@ void remove_proc_entry(const char *name, + de = NULL; + } else { + pde_erase(de, parent); +- if (S_ISDIR(de->mode)) +- parent->nlink--; + } + } + write_unlock(&proc_subdir_lock); +@@ -787,8 +788,6 @@ int remove_proc_subtree(const char *name + continue; + } + next = de->parent; +- if (S_ISDIR(de->mode)) +- next->nlink--; + write_unlock(&proc_subdir_lock); + + proc_entry_rundown(de); diff --git a/queue-7.1/remoteproc-qcom-fix-leak-when-custom-dump_segments-addition-fails.patch b/queue-7.1/remoteproc-qcom-fix-leak-when-custom-dump_segments-addition-fails.patch new file mode 100644 index 0000000000..a70f2644a9 --- /dev/null +++ b/queue-7.1/remoteproc-qcom-fix-leak-when-custom-dump_segments-addition-fails.patch @@ -0,0 +1,63 @@ +From ecf9fc18e62c58eae1ceb65dab2bccb8a724de2d Mon Sep 17 00:00:00 2001 +From: Wasim Nazir +Date: Wed, 18 Mar 2026 17:19:16 +0530 +Subject: remoteproc: qcom: Fix leak when custom dump_segments addition fails + +From: Wasim Nazir + +commit ecf9fc18e62c58eae1ceb65dab2bccb8a724de2d upstream. + +Free allocated minidump_region 'name' in qcom_add_minidump_segments() +when failing before adding the region to 'dump_segments'. Otherwise, +the 'name' is not tracked and is never freed by qcom_minidump_cleanup(). + +Return error when adding to 'dump_segments' fails. + +Cc: stable@vger.kernel.org # v5.11 +Fixes: 8ed8485c4f05 ("remoteproc: qcom: Add capability to collect minidumps") +Reviewed-by: Mukesh Ojha +Signed-off-by: Wasim Nazir +Link: https://lore.kernel.org/r/20260318-rproc-memleak-v2-1-ade70ab858f2@oss.qualcomm.com +Signed-off-by: Bjorn Andersson +Signed-off-by: Greg Kroah-Hartman +--- + drivers/remoteproc/qcom_common.c | 14 ++++++++++---- + 1 file changed, 10 insertions(+), 4 deletions(-) + +--- a/drivers/remoteproc/qcom_common.c ++++ b/drivers/remoteproc/qcom_common.c +@@ -109,6 +109,7 @@ static int qcom_add_minidump_segments(st + struct minidump_region __iomem *ptr; + struct minidump_region region; + int seg_cnt, i; ++ int ret = 0; + dma_addr_t da; + size_t size; + char *name; +@@ -129,17 +130,22 @@ static int qcom_add_minidump_segments(st + if (le32_to_cpu(region.valid) == MINIDUMP_REGION_VALID) { + name = kstrndup(region.name, MAX_REGION_NAME_LENGTH - 1, GFP_KERNEL); + if (!name) { +- iounmap(ptr); +- return -ENOMEM; ++ ret = -ENOMEM; ++ break; + } + da = le64_to_cpu(region.address); + size = le64_to_cpu(region.size); +- rproc_coredump_add_custom_segment(rproc, da, size, rproc_dumpfn_t, name); ++ ret = rproc_coredump_add_custom_segment(rproc, da, size, rproc_dumpfn_t, ++ name); ++ if (ret) { ++ kfree(name); ++ break; ++ } + } + } + + iounmap(ptr); +- return 0; ++ return ret; + } + + void qcom_minidump(struct rproc *rproc, unsigned int minidump_id, diff --git a/queue-7.1/remoteproc-xlnx-check-remote-core-state.patch b/queue-7.1/remoteproc-xlnx-check-remote-core-state.patch new file mode 100644 index 0000000000..e963ccfef7 --- /dev/null +++ b/queue-7.1/remoteproc-xlnx-check-remote-core-state.patch @@ -0,0 +1,180 @@ +From a48df51d23138388900995add2854cda4aa68e55 Mon Sep 17 00:00:00 2001 +From: Tanmay Shah +Date: Tue, 28 Apr 2026 15:18:56 -0700 +Subject: remoteproc: xlnx: Check remote core state + +From: Tanmay Shah + +commit a48df51d23138388900995add2854cda4aa68e55 upstream. + +The remote state is set to RPROC_DETACHED if the resource table is found +in the memory. However, this can be wrong if the remote is not started, +but firmware is still loaded in the memory. Use PM_GET_NODE_STATUS call +to the firmware to request the state of the RPU node. If the RPU is +actually out of reset and running, only then move the remote state to +RPROC_DETACHED, otherwise keep the remote state to RPROC_OFFLINE. + +Signed-off-by: Tanmay Shah +Fixes: bca4b02ef92e ("remoteproc: xlnx: Add attach detach support") +Reviewed-by: Beleswar Padhi +Acked-by: Michal Simek +Cc: stable@vger.kernel.org +Link: https://lore.kernel.org/r/20260428221855.313752-1-tanmay.shah@amd.com +Signed-off-by: Mathieu Poirier +Signed-off-by: Greg Kroah-Hartman +--- + drivers/firmware/xilinx/zynqmp.c | 28 +++++++++++++++++++ + drivers/remoteproc/xlnx_r5_remoteproc.c | 46 +++++++++++++++++++++++++------- + include/linux/firmware/xlnx-zynqmp.h | 21 ++++++++++++++ + 3 files changed, 85 insertions(+), 10 deletions(-) + +--- a/drivers/firmware/xilinx/zynqmp.c ++++ b/drivers/firmware/xilinx/zynqmp.c +@@ -1451,6 +1451,34 @@ int zynqmp_pm_get_node_status(const u32 + EXPORT_SYMBOL_GPL(zynqmp_pm_get_node_status); + + /** ++ * zynqmp_pm_get_rpu_node_status - PM call to request a RPU node's current power state ++ * @node: ID of the RPU component or sub-system in question ++ * @status: Current operating state of the requested RPU node. ++ * @requirements: Current requirements asserted on the RPU node. ++ * @usage: Usage information, used for RPU slave nodes only: ++ * PM_USAGE_NO_MASTER - No master is currently using ++ * the node ++ * PM_USAGE_CURRENT_MASTER - Only requesting master is ++ * currently using the node ++ * PM_USAGE_OTHER_MASTER - Only other masters are ++ * currently using the node ++ * PM_USAGE_BOTH_MASTERS - Both the current and at least ++ * one other master is currently ++ * using the node ++ * ++ * Return: Returns status, either success or error+reason ++ */ ++int zynqmp_pm_get_rpu_node_status(const u32 node, u32 *const status, ++ u32 *const requirements, u32 *const usage) ++{ ++ if (zynqmp_pm_feature(PM_GET_NODE_STATUS) < PM_API_VERSION_2) ++ return -EOPNOTSUPP; ++ ++ return zynqmp_pm_get_node_status(node, status, requirements, usage); ++} ++EXPORT_SYMBOL_GPL(zynqmp_pm_get_rpu_node_status); ++ ++/** + * zynqmp_pm_force_pwrdwn - PM call to request for another PU or subsystem to + * be powered down forcefully + * @node: Node ID of the targeted PU or subsystem +--- a/drivers/remoteproc/xlnx_r5_remoteproc.c ++++ b/drivers/remoteproc/xlnx_r5_remoteproc.c +@@ -948,16 +948,6 @@ static struct zynqmp_r5_core *zynqmp_r5_ + goto free_rproc; + } + +- /* +- * If firmware is already available in the memory then move rproc state +- * to DETACHED. Firmware can be preloaded via debugger or by any other +- * agent (processors) in the system. +- * If firmware isn't available in the memory and resource table isn't +- * found, then rproc state remains OFFLINE. +- */ +- if (!zynqmp_r5_get_rsc_table_va(r5_core)) +- r5_rproc->state = RPROC_DETACHED; +- + r5_core->rproc = r5_rproc; + return r5_core; + +@@ -1210,6 +1200,7 @@ static int zynqmp_r5_core_init(struct zy + { + struct device *dev = cluster->dev; + struct zynqmp_r5_core *r5_core; ++ u32 req, usage, status; + int ret = -EINVAL, i; + + r5_core = cluster->r5_cores[0]; +@@ -1255,6 +1246,41 @@ static int zynqmp_r5_core_init(struct zy + ret = zynqmp_r5_get_sram_banks(r5_core); + if (ret) + return ret; ++ ++ /* ++ * It is possible that firmware is loaded into the memory, but ++ * RPU (remote) is not running. In such case, RPU state will be ++ * moved to RPROC_DETACHED wrongfully. To avoid it first make ++ * sure RPU is power-on and out of reset before parsing for the ++ * resource table. ++ */ ++ ret = zynqmp_pm_get_rpu_node_status(r5_core->pm_domain_id, ++ &status, &req, &usage); ++ if (ret) { ++ dev_warn(r5_core->dev, ++ "failed to get rpu node status, err %d\n", ret); ++ continue; ++ } ++ ++ /* ++ * If RPU state is power on and out of reset i.e. running, then ++ * assign RPROC_DETACHED state. If the RPU is not out of reset ++ * then do not attempt to attach to the remote processor. ++ */ ++ if (status == PM_NODE_RUNNING) { ++ /* ++ * Not all the firmware that is running on the remote ++ * core is expected to have the resource table. The ++ * firmware might not use RPMsg at all, and in that case ++ * resource table becomes irrelevant. However, we still ++ * need to make sure that running core is not reported ++ * as offline. so do not decide remote core state based ++ * on the resource table availability ++ */ ++ if (zynqmp_r5_get_rsc_table_va(r5_core)) ++ dev_dbg(r5_core->dev, "rsc tbl not found\n"); ++ r5_core->rproc->state = RPROC_DETACHED; ++ } + } + + return 0; +--- a/include/linux/firmware/xlnx-zynqmp.h ++++ b/include/linux/firmware/xlnx-zynqmp.h +@@ -543,6 +543,18 @@ enum pm_gem_config_type { + }; + + /** ++ * enum pm_node_status - Device node status provided by xilpm fw ++ * @PM_NODE_UNUSED: Device is not used ++ * @PM_NODE_RUNNING: Device is power-on and out of reset ++ * @PM_NODE_HALT: Device is power-on but in the reset state ++ */ ++enum pm_node_status { ++ PM_NODE_UNUSED = 0, ++ PM_NODE_RUNNING = 1, ++ PM_NODE_HALT = 12, ++}; ++ ++/** + * struct zynqmp_pm_query_data - PM query data + * @qid: query ID + * @arg1: Argument 1 of query data +@@ -630,6 +642,8 @@ int zynqmp_pm_set_rpu_mode(u32 node_id, + int zynqmp_pm_set_tcm_config(u32 node_id, enum rpu_tcm_comb tcm_mode); + int zynqmp_pm_get_node_status(const u32 node, u32 *const status, + u32 *const requirements, u32 *const usage); ++int zynqmp_pm_get_rpu_node_status(const u32 node, u32 *const status, ++ u32 *const requirements, u32 *const usage); + int zynqmp_pm_set_sd_config(u32 node, enum pm_sd_config_type config, u32 value); + int zynqmp_pm_set_gem_config(u32 node, enum pm_gem_config_type config, + u32 value); +@@ -938,6 +952,13 @@ static inline int zynqmp_pm_get_node_sta + { + return -ENODEV; + } ++ ++static inline int zynqmp_pm_get_rpu_node_status(const u32 node, u32 *const status, ++ u32 *const requirements, ++ u32 *const usage) ++{ ++ return -ENODEV; ++} + + static inline int zynqmp_pm_set_sd_config(u32 node, + enum pm_sd_config_type config, diff --git a/queue-7.1/riscv-cacheinfo-fix-node-reference-leak-in-populate_cache_leaves.patch b/queue-7.1/riscv-cacheinfo-fix-node-reference-leak-in-populate_cache_leaves.patch new file mode 100644 index 0000000000..d88de7b1dd --- /dev/null +++ b/queue-7.1/riscv-cacheinfo-fix-node-reference-leak-in-populate_cache_leaves.patch @@ -0,0 +1,41 @@ +From bf4a195f063b0a0805c1417f6aad1dd32ea48f0f Mon Sep 17 00:00:00 2001 +From: Zishun Yi +Date: Sat, 6 Jun 2026 20:17:58 -0600 +Subject: riscv: cacheinfo: Fix node reference leak in populate_cache_leaves + +From: Zishun Yi + +commit bf4a195f063b0a0805c1417f6aad1dd32ea48f0f upstream. + +Currently, the while loop drops the reference to prev in each iteration. +If the loop terminates early due to a break, the final of_node_put(np) +correctly drops the reference to the current node. + +However, if the loop terminates naturally because np == NULL, calling +of_node_put(np) is a no-op. This leaves the last valid node stored in +prev without its reference dropped, resulting in a node reference leak. + +Fix this by changing the final `of_node_put(np)` to `of_node_put(prev)`. + +Fixes: 94f9bf118f1e ("RISC-V: Fix of_node_* refcount") +Cc: stable@vger.kernel.org +Assisted-by: Gemini:gemini-3.1-pro +Signed-off-by: Zishun Yi +Link: https://patch.msgid.link/20260509074040.1747800-1-vulab@iscas.ac.cn +Signed-off-by: Paul Walmsley +Signed-off-by: Greg Kroah-Hartman +--- + arch/riscv/kernel/cacheinfo.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/arch/riscv/kernel/cacheinfo.c ++++ b/arch/riscv/kernel/cacheinfo.c +@@ -133,7 +133,7 @@ int populate_cache_leaves(unsigned int c + ci_leaf_init(this_leaf++, CACHE_TYPE_DATA, level); + levels = level; + } +- of_node_put(np); ++ of_node_put(prev); + + return 0; + } diff --git a/queue-7.1/scsi-sas-skip-opt_sectors-when-dma-reports-no-real-optimization-hint.patch b/queue-7.1/scsi-sas-skip-opt_sectors-when-dma-reports-no-real-optimization-hint.patch new file mode 100644 index 0000000000..8447dc425e --- /dev/null +++ b/queue-7.1/scsi-sas-skip-opt_sectors-when-dma-reports-no-real-optimization-hint.patch @@ -0,0 +1,143 @@ +From be8fcd4a8217a916344c88a4b1b84f5736dda17e Mon Sep 17 00:00:00 2001 +From: Ionut Nechita +Date: Tue, 19 May 2026 16:52:33 +0300 +Subject: scsi: sas: Skip opt_sectors when DMA reports no real optimization hint +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +From: Ionut Nechita + +commit be8fcd4a8217a916344c88a4b1b84f5736dda17e upstream. + +sas_host_setup() unconditionally sets shost->opt_sectors from +dma_opt_mapping_size(). + +When the IOMMU is disabled or in passthrough mode and no DMA ops provide +an opt_mapping_size callback, dma_opt_mapping_size() returns +min(dma_max_mapping_size(), SIZE_MAX) which equals +dma_max_mapping_size() — a hard upper bound, not an optimization hint. + +On a Dell PowerEdge R750 with mpt3sas (Broadcom SAS3816, FW 33.15.00.00) +and intel_iommu=off the following values are observed: + + dma_opt_mapping_size() = dma_max_mapping_size() (no real hint) + shost->max_sectors = 32767 + opt_sectors = min(32767, huge >> 9) = 32767 + optimal_io_size = 32767 << 9 = 16776704 + → round_down(16776704, 4096) = 16773120 + +The SAS disk (SAMSUNG MZILT800HBHQ0D3) does not report an Optimal +Transfer Length in VPD page B0, so sdkp->opt_xfer_blocks remains 0. + +sd_revalidate_disk() then uses min_not_zero(0, opt_sectors) = +opt_sectors, propagating the bogus value into the block device's +optimal_io_size (visible as OPT-IO = 16773120 in lsblk --topology). + +mkfs.xfs picks up optimal_io_size and minimum_io_size and computes: + + swidth = 16773120 / 4096 = 4095 + sunit = 8192 / 4096 = 2 + +Since 4095 % 2 != 0, XFS rejects the geometry: + + SB stripe unit sanity check failed + +This makes it impossible to create XFS filesystems (e.g. for +/var/lib/docker) during system bootstrap. + +Fix this by introducing a sas_dma_setup_opt_sectors() helper that sets +opt_sectors only when dma_opt_mapping_size() is strictly less than +dma_max_mapping_size(), indicating a genuine DMA optimization +constraint. + +The helper computes min(opt_sectors, max_sectors) first, then rounds +down to a power of two so that filesystem geometry calculations always +produce clean results. + +When the two DMA values are equal, no backend provided a real hint, so +opt_sectors stays at 0 ("no preference"). + +[mkp: implemented hch's suggestion] + +Fixes: 4cbfca5f7750 ("scsi: scsi_transport_sas: cap shost opt_sectors according to DMA optimal limit") +Cc: stable@vger.kernel.org +Reviewed-by: John Garry +Signed-off-by: Ionut Nechita +Reviewed-by: Christoph Hellwig +Link: https://patch.msgid.link/20260519135238.373784-2-ionut.nechita@windriver.com +Signed-off-by: Martin K. Petersen +Signed-off-by: Greg Kroah-Hartman +--- + drivers/scsi/scsi_transport_sas.c | 41 +++++++++++++++++++++++++++++++++----- + 1 file changed, 36 insertions(+), 5 deletions(-) + +--- a/drivers/scsi/scsi_transport_sas.c ++++ b/drivers/scsi/scsi_transport_sas.c +@@ -27,6 +27,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -220,12 +221,45 @@ static int sas_bsg_initialize(struct Scs + * SAS host attributes + */ + ++/* ++ * Set shost->opt_sectors from the DMA optimal mapping size, but only ++ * when dma_opt_mapping_size() is strictly less than dma_max_mapping_size(), ++ * indicating a genuine optimization hint from an IOMMU or DMA backend. ++ * When the two are equal (e.g. IOMMU disabled / passthrough), no real ++ * hint exists, so leave opt_sectors at 0 to avoid bogus optimal_io_size ++ * values that break filesystem geometry (e.g. mkfs.xfs stripe alignment). ++ */ ++static void sas_dma_setup_opt_sectors(struct Scsi_Host *shost) ++{ ++ struct device *dma_dev = shost->dma_dev; ++ size_t opt = dma_opt_mapping_size(dma_dev); ++ size_t max = dma_max_mapping_size(dma_dev); ++ unsigned int opt_sectors; ++ ++ /* opt >= max means no real hint was provided by the DMA layer */ ++ if (opt >= max) ++ return; ++ ++ /* Clamp to max_sectors to avoid overflow in sector arithmetic */ ++ opt_sectors = min_t(unsigned int, opt >> SECTOR_SHIFT, ++ shost->max_sectors); ++ ++ /* Guard against zero before rounddown_pow_of_two() */ ++ if (!opt_sectors) ++ return; ++ ++ /* ++ * Round down to power-of-two so filesystem geometry calculations ++ * (e.g. XFS stripe width/unit) always produce clean divisors. ++ */ ++ shost->opt_sectors = rounddown_pow_of_two(opt_sectors); ++} ++ + static int sas_host_setup(struct transport_container *tc, struct device *dev, + struct device *cdev) + { + struct Scsi_Host *shost = dev_to_shost(dev); + struct sas_host_attrs *sas_host = to_sas_host_attrs(shost); +- struct device *dma_dev = shost->dma_dev; + + INIT_LIST_HEAD(&sas_host->rphy_list); + mutex_init(&sas_host->lock); +@@ -237,10 +271,7 @@ static int sas_host_setup(struct transpo + dev_printk(KERN_ERR, dev, "fail to a bsg device %d\n", + shost->host_no); + +- if (dma_dev->dma_mask) { +- shost->opt_sectors = min_t(unsigned int, shost->max_sectors, +- dma_opt_mapping_size(dma_dev) >> SECTOR_SHIFT); +- } ++ sas_dma_setup_opt_sectors(shost); + + return 0; + } diff --git a/queue-7.1/scsi-smartpqi-use-shost_to_hba-in-pqi_scan_finished.patch b/queue-7.1/scsi-smartpqi-use-shost_to_hba-in-pqi_scan_finished.patch new file mode 100644 index 0000000000..4f86cc3ae6 --- /dev/null +++ b/queue-7.1/scsi-smartpqi-use-shost_to_hba-in-pqi_scan_finished.patch @@ -0,0 +1,41 @@ +From 57db1307afb1f83d45f5ff53b93f8d040100d13e Mon Sep 17 00:00:00 2001 +From: Martin Wilck +Date: Wed, 13 May 2026 19:42:35 +0200 +Subject: scsi: smartpqi: Use shost_to_hba() in pqi_scan_finished() + +From: Martin Wilck + +commit 57db1307afb1f83d45f5ff53b93f8d040100d13e upstream. + +shost_to_hba() is used everywhere except to obtain pqi_ctrl_info from +shosti, except in pqi_scan_finished(), where shost_priv() is used. This +causes one pointer dereference to be missed, as shost->hostdata is a +pointer in smartpqi. Fix it. + +Fixes: 6c223761eb54 ("smartpqi: initial commit of Microsemi smartpqi driver") +Signed-off-by: Martin Wilck +Reviewed-by: Don Brace +Cc: Don Brace +Cc: storagedev@microchip.com +Cc: stable@vger.kernel.org +Reviewed-by: Hannes Reinecke +Reviewed-by: Hannes Reinecke +Reviewed-by: Christoph Hellwig +Link: https://patch.msgid.link/20260513174236.430465-2-mwilck@suse.com +Signed-off-by: Martin K. Petersen +Signed-off-by: Greg Kroah-Hartman +--- + drivers/scsi/smartpqi/smartpqi_init.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/scsi/smartpqi/smartpqi_init.c ++++ b/drivers/scsi/smartpqi/smartpqi_init.c +@@ -2642,7 +2642,7 @@ static int pqi_scan_finished(struct Scsi + { + struct pqi_ctrl_info *ctrl_info; + +- ctrl_info = shost_priv(shost); ++ ctrl_info = shost_to_hba(shost); + + return !mutex_is_locked(&ctrl_info->scan_mutex); + } diff --git a/queue-7.1/selftests-landlock-test-scope_signal-on-the-sigio-fowner-pgid-path.patch b/queue-7.1/selftests-landlock-test-scope_signal-on-the-sigio-fowner-pgid-path.patch new file mode 100644 index 0000000000..4e48a40c1b --- /dev/null +++ b/queue-7.1/selftests-landlock-test-scope_signal-on-the-sigio-fowner-pgid-path.patch @@ -0,0 +1,231 @@ +From 76579d09beedaffe7fe76e9c05644f73983e1ceb Mon Sep 17 00:00:00 2001 +From: Bryam Vargas +Date: Thu, 4 Jun 2026 23:17:05 +0000 +Subject: selftests/landlock: Test SCOPE_SIGNAL on the SIGIO/fowner pgid path +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +From: Bryam Vargas + +commit 76579d09beedaffe7fe76e9c05644f73983e1ceb upstream. + +Add regression tests for the LANDLOCK_SCOPE_SIGNAL handling of the +asynchronous SIGIO delivery path (fcntl(F_SETOWN)) with a process-group +owner. + +sigio_to_pgid_members covers the bypass: a sandboxed process at the head +of its process group's PGID hlist (the default after fork()) arms +F_SETOWN(-pgrp) + O_ASYNC and triggers the fan-out; the in-domain owner +must be signaled (proving the trigger fired) while the non-sandboxed +member of the group, outside the domain, must not. + +sigio_to_pgid_self covers the same-process guarantee: the owner is +registered from a sandboxed non-leader thread, whose domain differs from +the thread-group leader the kernel signals for a process-group owner. +That leader belongs to the owner's own process and must still be +signaled. + +Without the fix the first test sees the out-of-domain member signaled +and the second sees the owner's own leader denied. + +Cc: stable@vger.kernel.org +Signed-off-by: Bryam Vargas +Reviewed-by: Günther Noack +Link: https://patch.msgid.link/43370e89f7a896a583bf33d1cd171d02630e61bf.1780614610.git.hexlabsecurity@proton.me +[mic: Fix comment] +Signed-off-by: Mickaël Salaün +Signed-off-by: Greg Kroah-Hartman +--- + tools/testing/selftests/landlock/scoped_signal_test.c | 182 ++++++++++++++++++ + 1 file changed, 182 insertions(+) + +--- a/tools/testing/selftests/landlock/scoped_signal_test.c ++++ b/tools/testing/selftests/landlock/scoped_signal_test.c +@@ -559,4 +559,186 @@ TEST_F(fown, sigurg_socket) + _metadata->exit_code = KSFT_FAIL; + } + ++/* ++ * Checks that LANDLOCK_SCOPE_SIGNAL is enforced on the asynchronous SIGIO ++ * delivery path (fcntl(F_SETOWN)) when the file owner is a process group. ++ * ++ * A sandboxed process sitting at the head of its process group's PID hlist (the ++ * default position right after fork()) used to escape the fcntl(F_SETOWN, ++ * -pgrp) domain recording: pid_task(pgrp, PIDTYPE_PGID) resolved to the process ++ * itself, so the same-thread-group exemption skipped recording its Landlock ++ * domain. At SIGIO time that domain was then unset and the signal fanned out ++ * to every group member, including non-sandboxed processes outside the domain. ++ */ ++TEST(sigio_to_pgid_members) ++{ ++ int trigger[2], sync_child[2]; ++ char buf; ++ pid_t child; ++ int status, i; ++ ++ drop_caps(_metadata); ++ ++ /* ++ * Isolates the test in its own process group so the SIGIO fan-out stays ++ * bounded to this parent and the child forked below. ++ */ ++ ASSERT_EQ(0, setpgid(0, 0)); ++ ++ /* The non-sandboxed parent is the protected (out-of-domain) target. */ ++ ASSERT_EQ(0, setup_signal_handler(SIGURG)); ++ signal_received = 0; ++ ++ ASSERT_EQ(0, pipe2(trigger, O_CLOEXEC)); ++ ASSERT_EQ(0, pipe2(sync_child, O_CLOEXEC)); ++ ++ child = fork(); ++ ASSERT_LE(0, child); ++ if (child == 0) { ++ /* ++ * The child inherits the parent's new process group and, just ++ * attached with hlist_add_head_rcu(), is now the head of the ++ * pgid hlist: this is the case that used to skip the recording. ++ */ ++ EXPECT_EQ(0, close(sync_child[0])); ++ ++ /* In-domain positive control: the child must be signaled. */ ++ ASSERT_EQ(0, setup_signal_handler(SIGURG)); ++ signal_received = 0; ++ ++ create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL); ++ ++ /* Owns the SIGIO source for the whole process group. */ ++ ASSERT_EQ(0, fcntl(trigger[0], F_SETSIG, SIGURG)); ++ ASSERT_EQ(0, fcntl(trigger[0], F_SETOWN, -getpgrp())); ++ ASSERT_EQ(0, fcntl(trigger[0], F_SETFL, O_ASYNC)); ++ ++ /* Fans SIGURG out to every member of the process group. */ ++ ASSERT_EQ(1, write(trigger[1], ".", 1)); ++ ++ /* ++ * The sandboxed child is in its own domain and must always be ++ * signaled: this proves the SIGIO actually fired. ++ */ ++ for (i = 0; i < 1000 && !signal_received; i++) ++ usleep(1000); ++ EXPECT_EQ(1, signal_received); ++ ++ ASSERT_EQ(1, write(sync_child[1], ".", 1)); ++ EXPECT_EQ(0, close(sync_child[1])); ++ ++ _exit(_metadata->exit_code); ++ return; ++ } ++ EXPECT_EQ(0, close(sync_child[1])); ++ EXPECT_EQ(0, close(trigger[0])); ++ EXPECT_EQ(0, close(trigger[1])); ++ ++ /* Waits for the child to generate the SIGIO. */ ++ ASSERT_EQ(1, read(sync_child[0], &buf, 1)); ++ EXPECT_EQ(0, close(sync_child[0])); ++ ++ /* Lets a delivered-but-pending signal run our handler, if any. */ ++ for (i = 0; i < 100 && !signal_received; i++) ++ usleep(1000); ++ ++ /* ++ * SCOPE_SIGNAL must block the fan-out to this non-sandboxed parent, ++ * which is outside the child's Landlock domain. Before the fix the ++ * parent was signaled here. ++ */ ++ EXPECT_EQ(0, signal_received); ++ ++ ASSERT_EQ(child, waitpid(child, &status, 0)); ++ if (WIFSIGNALED(status) || !WIFEXITED(status) || ++ WEXITSTATUS(status) != EXIT_SUCCESS) ++ _metadata->exit_code = KSFT_FAIL; ++} ++ ++static void *thread_setown_scoped(void *arg) ++{ ++ const int fd = *(int *)arg; ++ int ruleset_fd; ++ const struct landlock_ruleset_attr ruleset_attr = { ++ .scoped = LANDLOCK_SCOPE_SIGNAL, ++ }; ++ ++ /* Sandboxes only this non-leader thread (no thread syncing). */ ++ ruleset_fd = ++ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); ++ if (ruleset_fd < 0) ++ return (void *)THREAD_ERROR; ++ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) || ++ landlock_restrict_self(ruleset_fd, 0)) { ++ close(ruleset_fd); ++ return (void *)THREAD_ERROR; ++ } ++ close(ruleset_fd); ++ ++ /* Makes this process group own the SIGIO source. */ ++ if (fcntl(fd, F_SETSIG, SIGURG) || fcntl(fd, F_SETOWN, -getpgrp()) || ++ fcntl(fd, F_SETFL, O_ASYNC)) ++ return (void *)THREAD_ERROR; ++ ++ return (void *)THREAD_SUCCESS; ++} ++ ++/* ++ * Checks that the SIGIO fan-out is still delivered to the file owner's own ++ * process when fcntl(F_SETOWN, -pgrp) was issued from a sandboxed non-leader ++ * thread. ++ * ++ * The Landlock domain is recorded for a process-group owner (so out-of-domain ++ * members stay blocked, see sigio_to_pgid_members), but the kernel signals a ++ * process group through its members' thread-group leaders. Here the leader is ++ * not sandboxed and thus has a different domain than the registering thread, so ++ * the registration-time check cannot tell that it belongs to the owner's own ++ * process. hook_file_send_sigiotask() must recognize it through the recorded ++ * thread group and allow the delivery, matching the same-process guarantee of ++ * commit 18eb75f3af40. Without that exemption the leader is wrongly denied and ++ * never signaled. ++ */ ++TEST(sigio_to_pgid_self) ++{ ++ int trigger[2]; ++ pthread_t thread; ++ enum thread_return ret = THREAD_INVALID; ++ int i; ++ ++ drop_caps(_metadata); ++ ++ /* Bounds the SIGIO fan-out to this process. */ ++ ASSERT_EQ(0, setpgid(0, 0)); ++ ++ /* The non-sandboxed thread-group leader is the SIGIO target. */ ++ ASSERT_EQ(0, setup_signal_handler(SIGURG)); ++ signal_received = 0; ++ ++ ASSERT_EQ(0, pipe2(trigger, O_CLOEXEC)); ++ ++ /* ++ * Registers the process-group fowner from a sibling thread that ++ * sandboxes only itself, so its domain differs from the leader's. ++ */ ++ ASSERT_EQ(0, pthread_create(&thread, NULL, thread_setown_scoped, ++ &trigger[0])); ++ ASSERT_EQ(0, pthread_join(thread, (void **)&ret)); ++ ASSERT_EQ(THREAD_SUCCESS, ret); ++ ++ /* Fans SIGURG out to the process group. */ ++ ASSERT_EQ(1, write(trigger[1], ".", 1)); ++ ++ for (i = 0; i < 1000 && !signal_received; i++) ++ usleep(1000); ++ ++ /* ++ * Same-process delivery must always be allowed, even though the owner ++ * was registered from a sandboxed sibling thread. ++ */ ++ EXPECT_EQ(1, signal_received); ++ ++ EXPECT_EQ(0, close(trigger[0])); ++ EXPECT_EQ(0, close(trigger[1])); ++} ++ + TEST_HARNESS_MAIN diff --git a/queue-7.1/series b/queue-7.1/series index 81e93372c8..3f63ae530d 100644 --- a/queue-7.1/series +++ b/queue-7.1/series @@ -1676,3 +1676,92 @@ sunrpc-harden-rq_procinfo-lifecycle-to-prevent-double-free.patch lockd-plug-nlm_file-leak-when-nlm_do_fopen-fails.patch lockd-plug-nlm_file-refcount-leak-on-cached-nlm_do_fopen-failure.patch sunrpc-bound-check-xdr_buf_to_bvec-stores-before-writing.patch +remoteproc-qcom-fix-leak-when-custom-dump_segments-addition-fails.patch +remoteproc-xlnx-check-remote-core-state.patch +mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch +mips-ip22-gio-fix-gio-device-memory-leak.patch +mips-ip22-gio-fix-kfree-of-static-object.patch +mips-ip22-gio-fix-device-reference-leak-in-probe.patch +mips-dec-ensure-32-bit-stack-location-for-o32-prom_printf.patch +mm-mm_init-fix-pageblock-migratetype-for-zone_device-compound-pages.patch +power-supply-cpcap-battery-fix-missing-nvmem_device_put-causing-reference-leak.patch +power-supply-max17042-fix-of-node-reference-imbalance.patch +mm-hugetlb-fix-hugetlb-cgroup-rsvd-charge-uncharge-mismatch.patch +mm-memory_hotplug-fix-incorrect-altmap-passing-in-error-path.patch +mm-damon-core-make-charge_addr_from-aware-of-end-address-exclusivity.patch +ntfs-grow-index-root-value-before-reparent-header-update.patch +ntfs-fix-incorrect-size-of-symbolic-link.patch +ntfs-fix-off-by-one-in-mapping-pairs-decoding-bounds-checks.patch +ntfs-add-wq_percpu-to-alloc_workqueue-users.patch +ntfs-validate-attribute-values-on-lookup.patch +ntfs-add-bounds-check-before-accessing-ea-entries.patch +ntfs-not-change-0-byte-data-attribute-to-non-resident.patch +ntfs-validate-index-block-header-more-strictly.patch +ntfs-free-volume-wide-resources-on-fill_super-failure.patch +ntfs-update-index-root-allocated-size-before-shrink.patch +ntfs-centalize-index_root-header-validation.patch +ntfs-skip-extent-mft-records-in-writeback-to-prevent-deadlock.patch +ntfs-reinit-search-context-before-volume-information-lookup.patch +ntfs-only-alias-volume-upcase-to-default-on-exact-match.patch +ntfs-avoid-heap-allocation-for-free-cluster-readahead-state.patch +ntfs-validate-index-entries-on-reading.patch +ntfs-detect-mapping-pairs-lcn-accumulator-overflow.patch +ntfs-do-not-replace-volume-name-after-lookup-errors.patch +ntfs-validate-resident-volume-name-values-on-lookup.patch +ntfs-validate-resident-index-root-values-on-lookup.patch +ntfs-reject-non-resident-records-for-resident-only-attributes.patch +fs-ntfs3-fix-syncing-wrong-inode-on-dirsync-cross-directory-rename.patch +fs-ntfs3-bound-deleteindexentryallocation-memmove-length.patch +fs-ntfs3-bound-copy_lcns-dp-page_lcns-index-in-analysis-pass.patch +fs-ntfs3-bound-attr_off-in-updateresidentvalue-against-data_off.patch +fs-ntfs3-validate-lcns_follow-in-log_replay-conversion.patch +fs-ntfs3-add-depth-limit-to-indx_find_buffer-to-prevent-stack-overflow.patch +fs-ntfs3-bound-ntfs_de-view.data_off-in-updaterecorddata-root-allocation.patch +ntfs3-cap-restart_table-free-chain-walker-at-rt-used.patch +ntfs3-bound-to_move-in-indx_insert_into_root-before-hdr_insert_head.patch +ntfs3-validate-split-point-offset-in-indx_insert_into_buffer.patch +ntfs3-fix-out-of-bounds-read-in-decompress_lznt.patch +ntfs-fix-mrec_lock-abba-deadlock-in-rename.patch +ntfs-fail-attrlist-updates-when-the-superblock-is-inactive.patch +ntfs-sanitize-mft-references-returned-from-ntfs_lookup_inode_by_name.patch +ntfs-avoid-self-deadlock-during-inode-eviction.patch +ntfs-make-system-files-immutable-to-prevent-corruption.patch +ntfs-fix-warn_on-for-resident-attribute-in-ntfs_map_runlist_nolock.patch +ntfs-fix-hole-runlist-memory-leak-in-insert-range-error-path.patch +landlock-fix-landlock_scope_signal-bypass-on-the-sigio-path.patch +power-supply-charger-manager-fix-refcount-leak-in-is_full_charged.patch +selftests-landlock-test-scope_signal-on-the-sigio-fowner-pgid-path.patch +mips-sched-fix-cpumask_offstack-memory-corruption.patch +mm-huge_memory-preserve-pmd_swp_uffd_wp-on-device-private-pmd-downgrade.patch +riscv-cacheinfo-fix-node-reference-leak-in-populate_cache_leaves.patch +mm-damon-core-always-put-unsuccessfully-committed-target-pids.patch +mm-damon-sysfs-schemes-fix-dir-put-orders-in-access_pattern_add_dirs.patch +mm-damon-sysfs-schemes-put-stats-for-scheme_add_dirs-internal-error.patch +fs-proc-task_mmu-fix-make_uffd_wp_huge_pte-prot-update-race.patch +fs-proc-task_mmu-fix-hugetlb-self-deadlock-in-pagemap_scan_pte_hole.patch +fs-proc-task_mmu-use-huge_page_size-in-pagemap_scan_hugetlb_entry.patch +fs-proc-task_mmu-do-not-warn-on-seeing-non-migration-pmd-entry.patch +proc-only-bump-parent-nlink-when-registering-directories.patch +fs-proc-fix-kpf_ksm-reported-for-all-anonymous-pages.patch +powerpc-dt_cpu_ftrs-set-cpu_ftr_p11_pvr-for-power11-and-later-processors.patch +mm-mm_init-fix-uninitialized-struct-pages-for-zone_device.patch +kcov-use-write_once-for-selftest-mode-stores.patch +mtd-slram-remove-failed-entries-from-the-device-list.patch +9p-skip-nlink-update-in-cacheless-mode-to-fix-warn_on.patch +power-supply-bq257xx-fix-vsysmin-clamping-logic.patch +scsi-smartpqi-use-shost_to_hba-in-pqi_scan_finished.patch +kho-make-sure-scratch-size-is-always-aligned-by-cma_min_alignment_bytes.patch +scsi-sas-skip-opt_sectors-when-dma-reports-no-real-optimization-hint.patch +openrisc-add-full-instruction-cache-invalidate-functions.patch +mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch +ocfs2-use-kzalloc-for-quota-recovery-bitmap-allocation.patch +mtd-rawnand-pl353-fix-probe-resource-allocation.patch +net-9p-fix-infinite-loop-in-p9_client_rpc-on-fatal-signal.patch +mtd-rawnand-fix-condition-in-nand_select_target.patch +ocfs2-avoid-moving-extents-to-occupied-clusters.patch +ocfs2-fix-null-h_transaction-deref-in-ocfs2_assure_trans_credits.patch +ocfs2-fix-ubsan-array-index-out-of-bounds-in-ocfs2_sum_rightmost_rec.patch +ocfs2-add-journal-null-check-in-ocfs2_checkpoint_inode.patch +ocfs2-reject-dinodes-with-non-canonical-i_mode-type.patch +ocfs2-reject-dinodes-whose-i_rdev-disagrees-with-the-file-type.patch +ocfs2-reject-non-inline-dinodes-with-i_size-and-zero-i_clusters.patch