Confirming this on different hardware, and I believe I have the root
cause.

System: Intel i7-3770 (Ivy Bridge, HD Graphics 4000, gen7), AnduinOS 2.0.1
(Ubuntu resolute base), kernel 7.0.0-29-generic, Mesa 26.0.3 (crocus).

Same signature as the original report - four hard freezes in three days, every
one of them with gnome-system-monitor as the faulting task:

    BUG: unable to handle page fault for address: fffff65289800008
    #PF: supervisor read access in kernel mode
    #PF: error_code(0x0000) - not-present page
    Oops: Oops: 0000 [#1] SMP NOPTI
    CPU: 4 UID: 1000 PID: 5892 Comm: gnome-system-mo Not tainted 
7.0.0-29-generic #29-Ubuntu
    RIP: 0010:kfree+0x60/0x400
    RBX: 00000000000002d8  R14: 0000000000000310
    Call Trace:
     <TASK>
     ? eb_parse+0x3d6/0x680 [i915]
     kvfree+0x32/0x40
     eb_relocate_parse_slow+0x217/0x370 [i915]
     i915_gem_do_execbuffer+0x638/0x13a0 [i915]
     i915_gem_execbuffer2_ioctl+0x137/0x260 [i915]
     drm_ioctl_kernel+0xb5/0x110
     drm_ioctl+0x309/0x5f0
     __x64_sys_ioctl+0xa3/0x100
     do_syscall_64+0x105/0x5a0
     entry_SYSCALL_64_after_hwframe+0x76/0x7e
    note: gnome-system-mo[5892] exited with irqs disabled

The kernel dies with IRQs disabled, which is why the machine is completely
unresponsive and needs a power cycle.


ROOT CAUSE

i915_gem_execbuffer2_ioctl() allocates two spare slots but copies in only
count:

    exec2_list = kvmalloc_array(count + 2, eb_element_size(),
                                __GFP_NOWARN | GFP_KERNEL);
    ...
    copy_from_user(exec2_list, u64_to_user_ptr(args->buffers_ptr),
                   sizeof(*exec2_list) * count)

The comment above that allocation explains the extra slots are "for use by the
command parser", and then argues in the same paragraph that zeroing is
unnecessary "because the exec2_list part doesn't need to be, as it's
immediately overwritten by user data a few lines below". That is not true of
the spare slots the allocation was enlarged to provide - copy_from_user()
covers only count entries, so exec[count] keeps whatever was in the heap.

eb_parse() then makes that slot reachable:

    eb->batches[0] = &eb->vma[eb->buffer_count++];

After that increment, every loop bounded by eb->buffer_count runs one entry
past the user-supplied data. The cleanup loop at the tail of
eb_relocate_parse_slow() is one of them:

    out:
        if (have_copy) {
            const unsigned int count = eb->buffer_count;
            for (i = 0; i < count; i++) {
                const struct drm_i915_gem_exec_object2 *entry = &eb->exec[i];
                if (!entry->relocation_count)
                    continue;
                relocs = u64_to_ptr(typeof(*relocs), entry->relocs_ptr);
                kvfree(relocs);
            }
        }

If the stale contents of that slot happen to have a non-zero
relocation_count, kvfree() is handed a garbage relocs_ptr. That is the fault.

The register state in the oops confirms the index.
sizeof(struct drm_i915_gem_exec_object2) is 56 (0x38):

    R14 = 0x310  ->  784 / 56 = 14  = eb->buffer_count
    RBX = 0x2d8  ->  728 / 56 = 13  = the faulting index, i.e. the appended slot

and eb_parse is still on the stack, which is what incremented the count.

This reproduces only on gen7/gen7.5 because those are the only platforms that
combine both preconditions: userspace still using GEM relocations (crocus),
which is what sets have_copy, and an enabled command parser, which is what
bumps buffer_count. The randomness comes from whatever previously occupied the
heap slot.

Note that the eb.vma half of this same allocation was fixed by commit
"drm/i915/gem: Zero-initialize the eb.vma array in i915_gem_do_execbuffer".
i915_gem_do_execbuffer() memsets eb.vma immediately after computing it, but
nothing memsets the exec2_list half. That fix addressed one half of the
problem and left the other.


TWO NOTES ON THE EARLIER TRIAGE IN THIS BUG

Testing 7.2 will not help. I diffed
drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c between v7.0 and 7.2-rc7: the
only changes are a redundant "else vma = NULL;" removal and a
dma_fence_array_create() signature change. The bug is present unchanged in
current mainline.

The 7.0.0-14 vs 7.0.0-27/-28 boundary is probably coincidence. I unpacked the
Ubuntu deltas for both linux_7.0.0-14.14.diff.gz and linux_7.0.0-29.29.diff.gz:
i915_gem_execbuffer.c is not patched in either, so this file is byte-identical
across that range. Given that the trigger depends on stale heap contents, a
long clean run on 7.0.0-14 is consistent with luck rather than a code
difference.


PATCH

Zero the spare slot, and bound the relocation cleanup by args->buffer_count,
since only user-supplied buffers can ever own a relocation array. Either hunk
alone stops the crash.

--- a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c
+++ b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c
@@ -1870,7 +1870,12 @@ err:

 out:
        if (have_copy) {
-               const unsigned int count = eb->buffer_count;
+               /*
+                * Only buffers passed in by userspace can own a relocation
+                * array; eb_parse() may have appended entries beyond them by
+                * incrementing eb->buffer_count.
+                */
+               const unsigned int count = eb->args->buffer_count;
                unsigned int i;

                for (i = 0; i < count; i++) {
@@ -3363,6 +3368,11 @@ i915_gem_do_execbuffer(struct drm_device *dev,
        eb.exec = exec;
        eb.vma = (struct eb_vma *)(exec + args->buffer_count + 1);
        memset(eb.vma, 0, (args->buffer_count + 1) * sizeof(struct eb_vma));
+       /*
+        * The spare exec slot is not covered by the copy_from_user() in
+        * i915_gem_execbuffer2_ioctl(), but eb_parse() makes it reachable by
+        * bumping eb.buffer_count. Do not let stale heap data through.
+        */
+       memset(exec + args->buffer_count, 0, sizeof(*exec));

        eb.batch_pool = NULL;

The memset is in bounds: the exec region spans indices 0..count and eb.vma
starts at exec + count + 1.

I have not yet run a patched module - I will follow up with test
results.

Workaround in the meantime: avoid gnome-system-monitor (btop/htop do not enter
this path), or run it with LIBGL_ALWAYS_SOFTWARE=1.

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

Title:
  Random hard freezes caused by i915 kernel page fault in
  eb_relocate_parse_slow on MacBookAir6,2 (Intel HD 5000)acBookAir6,2
  (Intel HD 5000)

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


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

Reply via email to