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