* FAILED: patch "[PATCH] fs/ntfs3: bound copy_lcns dp->page_lcns[] index in analysis" failed to apply to 5.15-stable tree
@ 2026-07-20 14:25 gregkh
2026-07-24 10:48 ` [PATCH 5.15.y 1/4] fs/ntfs3: Make ntfs_update_mftmirr return void Sasha Levin
0 siblings, 1 reply; 5+ messages in thread
From: gregkh @ 2026-07-20 14:25 UTC (permalink / raw)
To: michael.bommarito, 20260502154252.164586-1-jhapavitra98,
almaz.alexandrovich
Cc: stable
The patch below does not apply to the 5.15-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-5.15.y
git checkout FETCH_HEAD
git cherry-pick -x 5e7b598660cfa8e5af172cf4c65cffc126333307
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<stable@vger.kernel.org>' --in-reply-to '2026072042-litter-retold-6915@gregkh' --subject-prefix 'PATCH 5.15.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 5e7b598660cfa8e5af172cf4c65cffc126333307 Mon Sep 17 00:00:00 2001
From: Michael Bommarito <michael.bommarito@gmail.com>
Date: Fri, 15 May 2026 12:34:05 -0400
Subject: [PATCH] fs/ntfs3: bound copy_lcns dp->page_lcns[] index in analysis
pass
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 <michael.bommarito@gmail.com>
[almaz.alexandrovich@paragon-software.com: clang-formatted the changes,
fixed conflicts]
Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c
index 2bd8754ef26d..81a305d4bcf2 100644
--- a/fs/ntfs3/fslog.c
+++ b/fs/ntfs3/fslog.c
@@ -3394,8 +3394,8 @@ static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe,
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;
}
@@ -4585,22 +4585,34 @@ int log_replay(struct ntfs_inode *ni, bool *initialized)
* 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;
- }
+ 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];
- }
+ /*
+ * 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: {
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 5.15.y 1/4] fs/ntfs3: Make ntfs_update_mftmirr return void
2026-07-20 14:25 FAILED: patch "[PATCH] fs/ntfs3: bound copy_lcns dp->page_lcns[] index in analysis" failed to apply to 5.15-stable tree gregkh
@ 2026-07-24 10:48 ` Sasha Levin
2026-07-24 10:48 ` [PATCH 5.15.y 2/4] fs/ntfs3: extend ni_insert_nonresident to return inserted ATTR_LIST_ENTRY Sasha Levin
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Sasha Levin @ 2026-07-24 10:48 UTC (permalink / raw)
To: stable; +Cc: Pavel Skripkin, Konstantin Komarov, Sasha Levin
From: Pavel Skripkin <paskripkin@gmail.com>
[ Upstream commit e66af07ca2b57e01dbf6001ae565ab40037b2df3 ]
None of callers check the return value of ntfs_update_mftmirr(), so make
it return void to make code simpler.
Signed-off-by: Pavel Skripkin <paskripkin@gmail.com>
Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
Stable-dep-of: 5e7b598660cf ("fs/ntfs3: bound copy_lcns dp->page_lcns[] index in analysis pass")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/ntfs3/fsntfs.c | 20 +++++++-------------
fs/ntfs3/ntfs_fs.h | 2 +-
2 files changed, 8 insertions(+), 14 deletions(-)
diff --git a/fs/ntfs3/fsntfs.c b/fs/ntfs3/fsntfs.c
index 566dd9083b8ff4..ab9c75e092c299 100644
--- a/fs/ntfs3/fsntfs.c
+++ b/fs/ntfs3/fsntfs.c
@@ -830,7 +830,7 @@ int ntfs_refresh_zone(struct ntfs_sb_info *sbi)
/*
* ntfs_update_mftmirr - Update $MFTMirr data.
*/
-int ntfs_update_mftmirr(struct ntfs_sb_info *sbi, int wait)
+void ntfs_update_mftmirr(struct ntfs_sb_info *sbi, int wait)
{
int err;
struct super_block *sb = sbi->sb;
@@ -839,12 +839,12 @@ int ntfs_update_mftmirr(struct ntfs_sb_info *sbi, int wait)
u32 bytes;
if (!sb)
- return -EINVAL;
+ return;
blocksize = sb->s_blocksize;
if (!(sbi->flags & NTFS_FLAGS_MFTMIRR))
- return 0;
+ return;
err = 0;
bytes = sbi->mft.recs_mirr << sbi->record_bits;
@@ -855,16 +855,13 @@ int ntfs_update_mftmirr(struct ntfs_sb_info *sbi, int wait)
struct buffer_head *bh1, *bh2;
bh1 = sb_bread(sb, block1++);
- if (!bh1) {
- err = -EIO;
- goto out;
- }
+ if (!bh1)
+ return;
bh2 = sb_getblk(sb, block2++);
if (!bh2) {
put_bh(bh1);
- err = -EIO;
- goto out;
+ return;
}
if (buffer_locked(bh2))
@@ -884,13 +881,10 @@ int ntfs_update_mftmirr(struct ntfs_sb_info *sbi, int wait)
put_bh(bh2);
if (err)
- goto out;
+ return;
}
sbi->flags &= ~NTFS_FLAGS_MFTMIRR;
-
-out:
- return err;
}
/*
diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h
index d93cba03a65aae..a03dce278221e8 100644
--- a/fs/ntfs3/ntfs_fs.h
+++ b/fs/ntfs3/ntfs_fs.h
@@ -599,7 +599,7 @@ int ntfs_look_free_mft(struct ntfs_sb_info *sbi, CLST *rno, bool mft,
void ntfs_mark_rec_free(struct ntfs_sb_info *sbi, CLST rno, bool is_mft);
int ntfs_clear_mft_tail(struct ntfs_sb_info *sbi, size_t from, size_t to);
int ntfs_refresh_zone(struct ntfs_sb_info *sbi);
-int ntfs_update_mftmirr(struct ntfs_sb_info *sbi, int wait);
+void ntfs_update_mftmirr(struct ntfs_sb_info *sbi, int wait);
enum NTFS_DIRTY_FLAGS {
NTFS_DIRTY_CLEAR = 0,
NTFS_DIRTY_DIRTY = 1,
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 5.15.y 2/4] fs/ntfs3: extend ni_insert_nonresident to return inserted ATTR_LIST_ENTRY
2026-07-24 10:48 ` [PATCH 5.15.y 1/4] fs/ntfs3: Make ntfs_update_mftmirr return void Sasha Levin
@ 2026-07-24 10:48 ` Sasha Levin
2026-07-24 10:48 ` [PATCH 5.15.y 3/4] fs/ntfs3: Fix very fragmented case in attr_punch_hole Sasha Levin
2026-07-24 10:49 ` [PATCH 5.15.y 4/4] fs/ntfs3: bound copy_lcns dp->page_lcns[] index in analysis pass Sasha Levin
2 siblings, 0 replies; 5+ messages in thread
From: Sasha Levin @ 2026-07-24 10:48 UTC (permalink / raw)
To: stable; +Cc: Konstantin Komarov, Sasha Levin
From: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
[ Upstream commit c1e0ab3789215a3dfbe95f226955e93ea4803391 ]
Fixes xfstest generic/300
Fixes: 4534a70b7056 ("fs/ntfs3: Add headers and misc files")
Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
Stable-dep-of: 5e7b598660cf ("fs/ntfs3: bound copy_lcns dp->page_lcns[] index in analysis pass")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/ntfs3/attrib.c | 33 ++++++++++++++++++++-------------
fs/ntfs3/frecord.c | 4 ++--
fs/ntfs3/fslog.c | 25 +++++++++++++++++++------
fs/ntfs3/index.c | 2 +-
fs/ntfs3/ntfs_fs.h | 5 +++--
fs/ntfs3/run.c | 9 +++++++--
6 files changed, 52 insertions(+), 26 deletions(-)
diff --git a/fs/ntfs3/attrib.c b/fs/ntfs3/attrib.c
index 872586e9598a77..ca496499db71cb 100644
--- a/fs/ntfs3/attrib.c
+++ b/fs/ntfs3/attrib.c
@@ -327,7 +327,7 @@ int attr_make_nonresident(struct ntfs_inode *ni, struct ATTRIB *attr,
err = ni_insert_nonresident(ni, attr_s->type, attr_name(attr_s),
attr_s->name_len, run, 0, alen,
- attr_s->flags, &attr, NULL);
+ attr_s->flags, &attr, NULL, NULL);
if (err)
goto out3;
@@ -644,7 +644,7 @@ int attr_set_size(struct ntfs_inode *ni, enum ATTR_TYPE type,
/* Insert new attribute segment. */
err = ni_insert_nonresident(ni, type, name, name_len, run,
next_svcn, vcn - next_svcn,
- attr_b->flags, &attr, &mi);
+ attr_b->flags, &attr, &mi, NULL);
if (err)
goto out;
@@ -862,7 +862,7 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
goto out;
}
- asize = le64_to_cpu(attr_b->nres.alloc_size) >> sbi->cluster_bits;
+ asize = le64_to_cpu(attr_b->nres.alloc_size) >> cluster_bits;
if (vcn >= asize) {
err = -EINVAL;
goto out;
@@ -1054,7 +1054,7 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
if (evcn1 > next_svcn) {
err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run,
next_svcn, evcn1 - next_svcn,
- attr_b->flags, &attr, &mi);
+ attr_b->flags, &attr, &mi, NULL);
if (err)
goto out;
}
@@ -1673,7 +1673,7 @@ int attr_allocate_frame(struct ntfs_inode *ni, CLST frame, size_t compr_size,
if (evcn1 > next_svcn) {
err = ni_insert_nonresident(ni, ATTR_DATA, NULL, 0, run,
next_svcn, evcn1 - next_svcn,
- attr_b->flags, &attr, &mi);
+ attr_b->flags, &attr, &mi, NULL);
if (err)
goto out;
}
@@ -1840,18 +1840,12 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
err = ni_insert_nonresident(
ni, ATTR_DATA, NULL, 0, run, next_svcn,
evcn1 - eat - next_svcn, a_flags, &attr,
- &mi);
+ &mi, &le);
if (err)
goto out;
/* Layout of records maybe changed. */
attr_b = NULL;
- le = al_find_ex(ni, NULL, ATTR_DATA, NULL, 0,
- &next_svcn);
- if (!le) {
- err = -EINVAL;
- goto out;
- }
}
/* Free all allocated memory. */
@@ -1969,9 +1963,10 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size)
struct ATTRIB *attr = NULL, *attr_b;
struct ATTR_LIST_ENTRY *le, *le_b;
struct mft_inode *mi, *mi_b;
- CLST svcn, evcn1, vcn, len, end, alen, dealloc;
+ CLST svcn, evcn1, vcn, len, end, alen, dealloc, next_svcn;
u64 total_size, alloc_size;
u32 mask;
+ __le16 a_flags;
if (!bytes)
return 0;
@@ -2034,6 +2029,7 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size)
svcn = le64_to_cpu(attr_b->nres.svcn);
evcn1 = le64_to_cpu(attr_b->nres.evcn) + 1;
+ a_flags = attr_b->flags;
if (svcn <= vcn && vcn < evcn1) {
attr = attr_b;
@@ -2081,6 +2077,17 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size)
err = mi_pack_runs(mi, attr, run, evcn1 - svcn);
if (err)
goto out;
+ next_svcn = le64_to_cpu(attr->nres.evcn) + 1;
+ if (next_svcn < evcn1) {
+ err = ni_insert_nonresident(ni, ATTR_DATA, NULL,
+ 0, run, next_svcn,
+ evcn1 - next_svcn,
+ a_flags, &attr, &mi,
+ &le);
+ if (err)
+ goto out;
+ /* Layout of records maybe changed. */
+ }
}
/* Free all allocated memory. */
run_truncate(run, 0);
diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c
index 62874e7f8d8f17..81fc99f20c1169 100644
--- a/fs/ntfs3/frecord.c
+++ b/fs/ntfs3/frecord.c
@@ -1420,7 +1420,7 @@ int ni_insert_nonresident(struct ntfs_inode *ni, enum ATTR_TYPE type,
const __le16 *name, u8 name_len,
const struct runs_tree *run, CLST svcn, CLST len,
__le16 flags, struct ATTRIB **new_attr,
- struct mft_inode **mi)
+ struct mft_inode **mi, struct ATTR_LIST_ENTRY **le)
{
int err;
CLST plen;
@@ -1453,7 +1453,7 @@ int ni_insert_nonresident(struct ntfs_inode *ni, enum ATTR_TYPE type,
}
err = ni_insert_attr(ni, type, name, name_len, asize, name_off, svcn,
- &attr, mi, NULL);
+ &attr, mi, le);
if (err)
goto out;
diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c
index 9b12d5a7ac8d5e..09048409fe981d 100644
--- a/fs/ntfs3/fslog.c
+++ b/fs/ntfs3/fslog.c
@@ -3367,7 +3367,10 @@ static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe,
memmove(Add2Ptr(attr, aoff), data, dlen);
if (run_get_highest_vcn(le64_to_cpu(attr->nres.svcn),
- attr_run(attr), &t64)) {
+ attr_run(attr),
+ le32_to_cpu(attr->size) -
+ le16_to_cpu(attr->nres.run_off),
+ &t64)) {
goto dirty_vol;
}
@@ -4544,11 +4547,21 @@ int log_replay(struct ntfs_inode *ni, bool *initialized)
* whole routine a loop, case Lcns do not fit below.
*/
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];
- }
+ 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];
+ }
goto next_log_record_analyze;
diff --git a/fs/ntfs3/index.c b/fs/ntfs3/index.c
index b0aa5cc63b5079..9f2a1d41dfb5a0 100644
--- a/fs/ntfs3/index.c
+++ b/fs/ntfs3/index.c
@@ -1460,7 +1460,7 @@ static int indx_create_allocate(struct ntfs_index *indx, struct ntfs_inode *ni,
goto out;
err = ni_insert_nonresident(ni, ATTR_ALLOC, in->name, in->name_len,
- &run, 0, len, 0, &alloc, NULL);
+ &run, 0, len, 0, &alloc, NULL, NULL);
if (err)
goto out1;
diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h
index a03dce278221e8..85abc99a1e3bda 100644
--- a/fs/ntfs3/ntfs_fs.h
+++ b/fs/ntfs3/ntfs_fs.h
@@ -533,7 +533,7 @@ int ni_insert_nonresident(struct ntfs_inode *ni, enum ATTR_TYPE type,
const __le16 *name, u8 name_len,
const struct runs_tree *run, CLST svcn, CLST len,
__le16 flags, struct ATTRIB **new_attr,
- struct mft_inode **mi);
+ struct mft_inode **mi, struct ATTR_LIST_ENTRY **le);
int ni_insert_resident(struct ntfs_inode *ni, u32 data_size,
enum ATTR_TYPE type, const __le16 *name, u8 name_len,
struct ATTRIB **new_attr, struct mft_inode **mi,
@@ -802,7 +802,8 @@ int run_unpack_ex(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
#else
#define run_unpack_ex run_unpack
#endif
-int run_get_highest_vcn(CLST vcn, const u8 *run_buf, u64 *highest_vcn);
+int run_get_highest_vcn(CLST vcn, const u8 *run_buf, size_t run_buf_size,
+ u64 *highest_vcn);
/* Globals from super.c */
void *ntfs_set_shared(void *ptr, u32 bytes);
diff --git a/fs/ntfs3/run.c b/fs/ntfs3/run.c
index 10b1a2b08723b3..672115481fb07e 100644
--- a/fs/ntfs3/run.c
+++ b/fs/ntfs3/run.c
@@ -1105,18 +1105,23 @@ int run_unpack_ex(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
* Return the highest vcn from a mapping pairs array
* it used while replaying log file.
*/
-int run_get_highest_vcn(CLST vcn, const u8 *run_buf, u64 *highest_vcn)
+int run_get_highest_vcn(CLST vcn, const u8 *run_buf, size_t run_buf_size,
+ u64 *highest_vcn)
{
+ const u8 *run_last = run_buf + run_buf_size;
u64 vcn64 = vcn;
u8 size_size;
- while ((size_size = *run_buf & 0xF)) {
+ while (run_buf < run_last && (size_size = *run_buf & 0xF)) {
u8 offset_size = *run_buf++ >> 4;
u64 len;
if (size_size > 8 || offset_size > 8)
return -EINVAL;
+ if (run_buf + size_size + offset_size > run_last)
+ return -EINVAL;
+
len = run_unpack_s64(run_buf, size_size, 0);
if (!len)
return -EINVAL;
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 5.15.y 3/4] fs/ntfs3: Fix very fragmented case in attr_punch_hole
2026-07-24 10:48 ` [PATCH 5.15.y 1/4] fs/ntfs3: Make ntfs_update_mftmirr return void Sasha Levin
2026-07-24 10:48 ` [PATCH 5.15.y 2/4] fs/ntfs3: extend ni_insert_nonresident to return inserted ATTR_LIST_ENTRY Sasha Levin
@ 2026-07-24 10:48 ` Sasha Levin
2026-07-24 10:49 ` [PATCH 5.15.y 4/4] fs/ntfs3: bound copy_lcns dp->page_lcns[] index in analysis pass Sasha Levin
2 siblings, 0 replies; 5+ messages in thread
From: Sasha Levin @ 2026-07-24 10:48 UTC (permalink / raw)
To: stable; +Cc: Konstantin Komarov, Sasha Levin
From: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
[ Upstream commit 560f7736b94622c077344e2c541c43c63b4e90e4 ]
In some cases we need to ni_find_attr attr_b
Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
Stable-dep-of: 5e7b598660cf ("fs/ntfs3: bound copy_lcns dp->page_lcns[] index in analysis pass")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/ntfs3/attrib.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/fs/ntfs3/attrib.c b/fs/ntfs3/attrib.c
index ca496499db71cb..69e9ece120c8bd 100644
--- a/fs/ntfs3/attrib.c
+++ b/fs/ntfs3/attrib.c
@@ -2087,6 +2087,7 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size)
if (err)
goto out;
/* Layout of records maybe changed. */
+ attr_b = NULL;
}
}
/* Free all allocated memory. */
@@ -2106,6 +2107,14 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size)
}
total_size -= (u64)dealloc << sbi->cluster_bits;
+ if (!attr_b) {
+ attr_b = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL,
+ &mi_b);
+ if (!attr_b) {
+ err = -EINVAL;
+ goto out;
+ }
+ }
attr_b->nres.total_size = cpu_to_le64(total_size);
mi_b->dirty = true;
@@ -2116,8 +2125,10 @@ int attr_punch_hole(struct ntfs_inode *ni, u64 vbo, u64 bytes, u32 *frame_size)
out:
up_write(&ni->file.run_lock);
- if (err)
+ if (err) {
+ ntfs_set_state(sbi, NTFS_DIRTY_ERROR);
make_bad_inode(&ni->vfs_inode);
+ }
return err;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 5.15.y 4/4] fs/ntfs3: bound copy_lcns dp->page_lcns[] index in analysis pass
2026-07-24 10:48 ` [PATCH 5.15.y 1/4] fs/ntfs3: Make ntfs_update_mftmirr return void Sasha Levin
2026-07-24 10:48 ` [PATCH 5.15.y 2/4] fs/ntfs3: extend ni_insert_nonresident to return inserted ATTR_LIST_ENTRY Sasha Levin
2026-07-24 10:48 ` [PATCH 5.15.y 3/4] fs/ntfs3: Fix very fragmented case in attr_punch_hole Sasha Levin
@ 2026-07-24 10:49 ` Sasha Levin
2 siblings, 0 replies; 5+ messages in thread
From: Sasha Levin @ 2026-07-24 10:49 UTC (permalink / raw)
To: stable; +Cc: Michael Bommarito, Konstantin Komarov, Sasha Levin
From: Michael Bommarito <michael.bommarito@gmail.com>
[ Upstream commit 5e7b598660cfa8e5af172cf4c65cffc126333307 ]
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 <michael.bommarito@gmail.com>
[almaz.alexandrovich@paragon-software.com: clang-formatted the changes,
fixed conflicts]
Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/ntfs3/fslog.c | 46 +++++++++++++++++++++++++++++-----------------
1 file changed, 29 insertions(+), 17 deletions(-)
diff --git a/fs/ntfs3/fslog.c b/fs/ntfs3/fslog.c
index 09048409fe981d..8653c05e7b13e0 100644
--- a/fs/ntfs3/fslog.c
+++ b/fs/ntfs3/fslog.c
@@ -3368,8 +3368,8 @@ static int do_action(struct ntfs_log *log, struct OPEN_ATTR_ENRTY *oe,
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;
}
@@ -4547,22 +4547,34 @@ int log_replay(struct ntfs_inode *ni, bool *initialized)
* 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: {
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-07-24 10:49 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-20 14:25 FAILED: patch "[PATCH] fs/ntfs3: bound copy_lcns dp->page_lcns[] index in analysis" failed to apply to 5.15-stable tree gregkh
2026-07-24 10:48 ` [PATCH 5.15.y 1/4] fs/ntfs3: Make ntfs_update_mftmirr return void Sasha Levin
2026-07-24 10:48 ` [PATCH 5.15.y 2/4] fs/ntfs3: extend ni_insert_nonresident to return inserted ATTR_LIST_ENTRY Sasha Levin
2026-07-24 10:48 ` [PATCH 5.15.y 3/4] fs/ntfs3: Fix very fragmented case in attr_punch_hole Sasha Levin
2026-07-24 10:49 ` [PATCH 5.15.y 4/4] fs/ntfs3: bound copy_lcns dp->page_lcns[] index in analysis pass Sasha Levin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox