Reproduced in a VM and used to verify the patch proposed by Viktor Pashaiev.
Reproducer and both boot logs attached. All runs on 7.0.0-30-generic, the kernel
the original crash happened on.

## 1. Mechanism

attr_data_get_block_locked() duplicates the resident attribute:

    *res = kmemdup(resident_data(attr_b), data_size, GFP_KERNEL);

The pointer becomes iomap->inline_data, iomap->length becomes data_size.
iomap_write_end_inline() then asserts

    iomap->length <= PAGE_SIZE - offset_in_page(iomap->inline_data)

A slab object rarely starts on a page boundary. data_size 129..192 comes from
kmalloc-192: order=1 slab, 42 objects, 192-byte stride. Object #21 starts at
4032, and 4032 + (129..192) = 4161..4224 > 4096. The other 41 objects pass.

## 2. The guest needs >= 16 vCPUs

calculate_order() in mm/slub.c:

    nr_cpus     = num_present_cpus();
    min_objects = 4 * (fls(nr_cpus) + 1);
    min_order   = max(slub_min_order, get_order(min_objects * size));

size=192, 15 CPUs: fls(15)=4, min_objects=20, 20*192=3840, get_order(3840)=0,
min_order=0. calc_slab_order() at order 0: rem = 4096 % 192 = 64, threshold
slab_size/16 = 256, 64 <= 256 so it stops. order=0: one page, 21 objects,
offsets 0..3840, and 3840 + 192 = 4032 <= 4096. Nothing straddles a page
boundary; the BUG_ON is unreachable.

size=192, 16 CPUs: fls(16)=5, min_objects=24, 24*192=4608, get_order(4608)=1,
min_order=1. order=1: 42 objects, object #21 at 4032.

Threshold is exactly 16 present CPUs. Verify in the guest (mode 0400,
root):

    # cat /sys/kernel/slab/kmalloc-192/order          -> 1
    # cat /sys/kernel/slab/kmalloc-192/objs_per_slab  -> 42

Below that, a reproducer reports "not reproducible" on a broken kernel
too.

## 3. Workload

The original crash was a 137-byte write(2) to fd 1, rsync writing its log to an
ntfs3 volume. attr_set_size_res() only goes non-resident once
used + dsize > sbi->max_bytes_per_attr (~700 bytes at a 1024-byte MFT record),
so a small new file stays resident and its first write takes the resident 
branch.

Loop: create file, one write() of 129..192 bytes, unlink.

Two constraints, both of which produce zero hits if violated:

  * Appending (open(p,'ab')) writes past i_size, goes through ntfs_extend() ->
    ntfs_set_size(), and once non-resident kmemdup() is never called again.
  * Unrelated kmalloc-192 traffic is required. The buffer lives only from
    iomap_begin to iomap_end, so SLUB returns the same object LIFO and object
    #21 never surfaces.

Setup: qemu/KVM, 16 vCPUs, 4 GiB RAM, 4 GiB NTFS image, files seeded through
ntfs-3g, then remounted with ntfs3 rw.

## 4. Result on an unmodified kernel

Shipped ntfs3 module, BUG_ON 68.4 s after boot:

    kernel BUG at fs/iomap/buffered-io.c:1061!
    Oops: invalid opcode: 0000 [#1] SMP NOPTI
    CPU: 10 UID: 0 PID: 1788 Comm: python3 Not tainted 7.0.0-30-generic 
#30-Ubuntu
    RIP: 0010:iomap_write_end+0x1e0/0x1f0
    RAX: 0000000000000040 RBX: fffffb5d41097280 RCX: fffffb5d41097280
    RDX: 0000000000000fc0 RSI: 00000000000000b8 RDI: ffff8dc4cd852fc0
    Call Trace:
     iomap_write_iter+0x171/0x340
     iomap_file_buffered_write+0xa6/0x110
     ntfs_file_write_iter+0x267/0x310 [ntfs3]
     vfs_write+0x25b/0x490
     ksys_write+0x71/0xf0
     __x64_sys_write+0x19/0x30

Against the crash originally reported here:

                             original (rsync)   reproducer
    RDX = offset_in_page     0xfc0 = 4032       0xfc0 = 4032
    RAX = PAGE_SIZE - off    0x40  = 64         0x40  = 64
    write length             0x89  = 137        0xb8  = 184
    inline_data low bits     ...c55acfc0        ...cd852fc0

Same signature in bugzilla.kernel.org #221446 (Arch, 7.0.2, process `git`):
RDX 0xfc0, RAX 0x40, length 145. Three independent hits on offset 4032, all
lengths inside the kmalloc-192 range.

## 5. Patch verification

Built out of tree from linux-source-7.0.0 against 7.0.0-30-generic headers as
two modules differing only by Viktor's patch, which applied with no offsets and
no fuzz. Confirmed at machine-code level: in attr_data_get_block_locked() the
unpatched build calls kmemdup_noprof, the patched build calls alloc_pages_noprof
plus memcpy.

An unpatched out-of-tree build was run first as a control. Without it, "no panic
with the patch" would not distinguish a working fix from an out-of-tree build
that differs from the shipped module. Each run verified the live module by
comparing /sys/module/ntfs3/srcversion against the expected value.

Same kernel, same image, same workload. Only the module differs.

                                  control             patched
    live srcversion               99295717... (ok)    A80EE935... (ok)
    kmalloc-192 geometry          order=1 objs=42     order=1 objs=42
    run length                    60 s                840 s
    inline mappings observed      56,013,951          864,253,125
    offset_in_page values seen    76 distinct         only 0
    mappings violating the check  1                   0
    BUG_ON                        yes, at 29.4 s      none in 840 s

The @DANGER counter and the oops agree on the same mapping. In the control run
the probe fired on the return from ntfs_iomap_begin() with

    [DANGER] off=4032 len=160 sum=4192 -- BUG_ON next

and the oops milliseconds later reports RDX=0xfc0 (4032), RSI=0xa0 (160),
RAX=0x40 (4096 - 4032). In boot-vanilla.log that printf is interleaved
character by character with the oops text, since bpftrace and the kernel share
the console. So the counter that reads 0 across 864,253,125 mappings on the
patched build is measuring the exact condition that crashes.

The offset row is the load-bearing one. With the patch every inline mapping was
page aligned, so length <= PAGE_SIZE - 0 holds for any resident size (at most
~700 bytes against 4096). The failure mode is removed structurally, not made
rarer.

The BUG_ON itself is probabilistic, which matters for SRU verification. Four
unpatched runs here fired at 68.4 s, 25.7 s and 29.4 s after boot, and one did
not fire at all in 360 s despite 319,225,379 inline mappings and 85 distinct
offsets in that run. A violating mapping turns up on the order of once per a few
hundred million inline mappings, since it needs the allocation to land in object
#21 of a kmalloc-192 slab specifically. Absence of a BUG_ON in a single run is
therefore not evidence of a fix on its own; the offset distribution is the
reliable signal.

Instrumentation caveat: offset 4032 alone is not a fault indicator. It is also
the last object of a kmalloc-64 slab (63 * 64 = 4032), where 4032 + 64 = 4096
fits exactly. In longer runs most landings on 4032 were harmless for exactly
that reason. The condition to test is
length > PAGE_SIZE - offset_in_page(inline_data), which is what the @DANGER
counter in the attached bpftrace script evaluates.

## 6. Upstream

    70d3855594cf6e8791970714b65cac3202d6160e
    "ntfs3: Allocate iomap inline_data using alloc_page"
    Mihai Brodschi
    Fixes: 099ef9ab9203 ("fs/ntfs3: implement iomap-based file operations")

fs/ntfs3/attrib.c and fs/ntfs3/inode.c. Mainline v7.2, stable v7.1.5. Checked
both stable tags: fs/ntfs3/attrib.c in v7.1.4 still has
kmemdup(resident_data(...)), in v7.1.5 it has alloc_page().

099ef9ab9203, the iomap conversion, is what shipped in 7.0. 7.0.0-31.31 is at
"Upstream stable to v6.18.39, v7.1.4".

## Attachments

  * ntfs3-2165844-reproducer.tar.gz - workload scripts, bpftrace script, runner,
    module build script, README with the recipe and the prerequisites.
  * boot-vanilla.log - control run, unpatched (next comment).
  * boot-patched.log - patched run (next comment).

** Attachment added: "Reproducer: workload scripts, bpftrace instrumentation, 
VM runner, module build script, README"
   
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2165844/+attachment/6000954/+files/ntfs3-2165844-reproducer.tar.gz

-- 
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2165844

Title:
  linux 7.0.0-30: kernel BUG at fs/iomap/buffered-io.c:1061 in
  iomap_write_end() on ntfs3 buffered write

To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2165844/+subscriptions


-- 
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs

Reply via email to