Re: ntfs3: two out-of-bounds reads in $LogFile replay (restart page resize, restart table offset)
From: Guikun Yang
Date: Fri Nov 06 2026 - 23:29:40 EST
Hi,
Two more in the same path while re-checking it - one OOB read, one OOB
write. Same forged-logfile setup as before, mainline 2026-11-06
unchanged.
## 3. Undo pass indexes the open-attribute table without the bound the
redo pass has
The redo pass bounds target_attr (fs/ntfs3/fslog.c:5105):
t16 = le16_to_cpu(lrh->target_attr);
if (t16 >= bytes_per_rt(oatbl)) {
err = -EINVAL;
goto out;
}
oe = Add2Ptr(oatbl, t16);
The undo pass does not (fslog.c:5251):
oe = Add2Ptr(oatbl, le16_to_cpu(lrh->target_attr));
oa = oe->ptr;
check_log_rec() only validates target_attr when lcns_follow != 0, so a
record with undo_op != Noop, lcns_follow = 0 and target_attr beyond
the open-attribute table reaches oe->ptr outside the table:
BUG: KASAN: slab-out-of-bounds in log_replay+0xafda/0xf580
Read of size 8 ... located 104 bytes to the right of allocated
344-byte region (kmalloc-512)
Allocated by: init_rsttbl -> log_replay
In our run the table is 0x158 bytes and target_attr = 0x1A0.
Suggested fix: mirror the redo-pass check in the undo pass.
## 4. Dirty page table size underflow in the analysis pass ->
OOB write via copy_lcns()
The number of lcns per dirty-page entry is computed differently in
two places. The version-0 conversion has a guard (fslog.c:4357):
t16 = le16_to_cpu(dptbl->size);
if (t16 < sizeof(struct DIR_PAGE_ENTRY)) { ... goto out; }
t32 = (t16 - sizeof(struct DIR_PAGE_ENTRY)) / sizeof(u64);
The analysis pass does the same subtraction without the guard
(fslog.c:4666):
if (dptbl) {
t32 = (le16_to_cpu(dptbl->size) -
sizeof(struct DIR_PAGE_ENTRY)) / sizeof(u64);
}
dptbl->size is an on-disk u16. A table header of size = 0x10, total = 0
passes check_rstbl() (rsize nonzero, no allocated entries) and
check_dp_table() (nothing to enumerate). The unsigned subtraction then
wraps, and copy_lcns() copies lrh->lcns_follow = 0x60 entries into
dp->page_lcns[] of a table that extend_rsttbl() allocated for the
bogus per-entry size - the 29th entry writes past the end:
BUG: KASAN: slab-out-of-bounds in log_replay+0x8b01/0xf580
Write of size 8 ... located 0 bytes to the right of allocated
280-byte region (kmalloc-512)
Allocated by: extend_rsttbl -> log_replay
This is an OOB write, unlike the three reads reported so far.
Suggested fix: apply the same t16 < sizeof(struct DIR_PAGE_ENTRY)
guard (or reject size < sizeof(struct DIR_PAGE_ENTRY) in
check_rstbl()/check_dp_table() itself, which covers both sites).
## Context
Both reproduce 3/3 with panic_on_warn=1, mount(2) alone, clean image
mounts fine as control. The forged volume is the same one used for
reports 1 and 2 - each of the four findings differs from the last by
one field of one log record.
Four different missing-bound mechanisms are now known on this replay
path: buffer resizing (report 1), unbounded in-record offset (report
2), redo/undo asymmetry (3) and the version-0/analysis asymmetry plus
underflow (4). Given the pattern, a systematic pass over the remaining
offsets in log_replay() may be more effective than fixing these one by
one - happy to test any patch.
Both sites are unchanged in mainline as of 2026-11-06.
Thanks,
Guikun Yang