NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

kern/60291: i915drmkms kernel panic when booting with no monitor connected



>Number:         60291
>Category:       kern
>Synopsis:       i915drmkms kernel panic when booting with no monitor connected
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sun May 24 02:45:00 +0000 2026
>Originator:     sergio lenzi
>Release:        10.1_STABLE
>Organization:
k1 sistemas
>Environment:
NetBSD desktop2.lenzicasa 10.1_STABLE NetBSD 10.1_STABLE (LZT_2048_USERS) #2: Fri May 22 12:18:56 -03 2026  NetBSD@lztdev.lenzicasa:/home/NetBSD/BUILD/10/amd64/OBJ/sys/arch/amd64/compile/GENERIC amd6
>Description:
i915drmkms kernel panic when booting with no monitor connected
==============================================================

Problem
-------

NetBSD 10 panics during boot on systems with an Intel GPU using the
i915drmkms driver when no monitor is connected.  The panic is a NULL
pointer dereference in the framebuffer initialization path.

Root Cause
----------

The crash involves a race condition and missing NULL checks in the
NetBSD-specific DRM framebuffer glue code.  The sequence is:

1. intel_fbdev_initial_config() calls drm_fb_helper_initial_config(),
   which calls drm_fb_helper_single_fb_probe().

2. With no monitor connected, crtc_count is 0, so
   drm_fb_helper_single_fb_probe() returns -EAGAIN *without* calling
   the fb_probe callback (intelfb_create).

   File: dist/drm/drm_fb_helper.c, lines 1641-1647

3. __drm_fb_helper_initial_config_and_unlock() catches -EAGAIN, sets
   deferred_setup = true, and returns 0 (success).

   File: dist/drm/drm_fb_helper.c, lines 1823-1826

4. Because intelfb_create() never ran:
   - helper->fb is NULL
   - ifbdev->vma is NULL
   - No intelfb child device was created via config_found()

5. Any subsequent access to helper->fb or ifbdev->vma causes a NULL
   pointer dereference and kernel panic.

There is also a race condition that can trigger even WITH a monitor:

   In intelfb_create() (intel_fbdev.c, NetBSD section), config_found()
   is called at line 268, which triggers intelfb_attach(), which
   schedules the asynchronous intelfb_attach_task().  That task
   dereferences helper->fb->pitches[0] at intelfb.c line 138.  But
   helper->fb is not set until line 276, AFTER config_found() returns.
   If the async task runs before line 276 completes, it hits a NULL
   pointer dereference.

Crash Points
------------

1. intelfb.c line 138 (intelfb_attach_task):

     .da_fb_linebytes = ifa->ifa_fb_helper->fb->pitches[0],

   Dereferences helper->fb without a NULL check.  Crashes when
   intelfb_create was never called (headless) or when the async task
   wins the race against line 276 of intel_fbdev.c.

2. intelfb.c line 182 (intelfb_drmfb_mmapfb):

     KASSERT(offset < vma->node.size);

   Dereferences fbdev->vma without a NULL check.  Crashes if mmap is
   attempted before vma is initialized.

3. drmfb.c line 170 (drmfb_attach):

     KASSERTMSG(error == 0, "genfb_attach failed, error=%d", error);

   If genfb_attach() fails for any reason (e.g. zero-sized framebuffer),
   this KASSERT panics the kernel instead of returning an error.

Fix
---

Four changes in three NetBSD-specific files.  No changes to the
upstream-derived drm_fb_helper.c are needed; the deferred_setup
mechanism works correctly as designed.

Fix 1 - Eliminate the race condition (intel_fbdev.c)

   File: dist/drm/i915/display/intel_fbdev.c, lines 258-276
         (inside #ifdef __NetBSD__ block)

   Move the assignment:
     ifbdev->helper.fb = &ifbdev->fb->base;
   from line 276 (after config_found) to before the config_found()
   call at line 267.  This ensures helper->fb is valid before the
   async intelfb_attach_task can run.

   If config_found() fails, reset helper->fb to NULL before taking
   the error path.

Fix 2 - Add NULL check in intelfb_attach_task (intelfb.c)

   File: i915drm/intelfb.c, lines 127-157

   Before dereferencing helper->fb->pitches[0], check that helper->fb
   is not NULL.  If NULL, print an error and skip to the out label.
   This is defense-in-depth for Fix 1.

Fix 3 - Replace KASSERT with error return in drmfb_attach (drmfb.c)

   File: drm/drmfb.c, lines 98, 167-173

   Replace:
     KASSERTMSG(error == 0, "genfb_attach failed, error=%d", error);
   with:
     if (error) {
         aprint_error_dev(da->da_dev,
             "genfb_attach failed, error=%d\n", error);
         return error;
     }

   Also remove __diagused from the error variable declaration at
   line 98, since the variable is now unconditionally used.

Fix 4 - Add NULL guard in intelfb_drmfb_mmapfb (intelfb.c)

   File: i915drm/intelfb.c, lines 167-186

   After obtaining vma from fbdev->vma, check for NULL before the
   KASSERT that dereferences vma->node.size.  Return (paddr_t)-1
   on NULL, which is the standard mmap failure value.

Expected Behavior After Fix
---------------------------

Headless boot (no monitor):
  drm_fb_helper_initial_config returns 0 with deferred_setup=true.
  No intelfb child device is created.  VGA console remains active.
  The system boots to multiuser without panic.

Monitor hot-plugged after headless boot:
  intel_fbdev_output_poll_changed() detects deferred_setup==true,
  triggers drm_fb_helper_hotplug_event(), which re-enters the
  initial config path.  This time a monitor is present, so
  intelfb_create() runs successfully, helper->fb is set before
  config_found() (Fix 1), and the intelfb child attaches cleanly.

Normal boot (monitor connected):
  No change in behavior.  Fix 1 eliminates the pre-existing race
  condition.  Fixes 2-4 add defensive checks that do not affect
  the normal path.

Files Involved (all paths relative to sys/external/bsd/drm2/)
--------------------------------------------------------------

  Modified:
    dist/drm/i915/display/intel_fbdev.c   Fix 1
    i915drm/intelfb.c                     Fixes 2 and 4
    drm/drmfb.c                           Fix 3

  Reference only (not modified):
    dist/drm/drm_fb_helper.c              deferred_setup mechanism
    i915drm/intelfb.h                     intelfb_attach_args struct
    dist/drm/i915/display/intel_display_types.h  intel_fbdev struct
>How-To-Repeat:
find a cpu with intel graphics do not connect a monitor, and boot, it panics and keep rebooting... until you connect a monitor

solution was to disable i915drmkms in /boot.cfg

Now AI point to a possible solution...

Please someone can take a look???
>Fix:
--- a/sys/external/bsd/drm2/dist/drm/i915/display/intel_fbdev.c
+++ b/sys/external/bsd/drm2/dist/drm/i915/display/intel_fbdev.c
@@ -255,7 +255,14 @@
 	ifa.ifa_fb_helper = helper;
 	ifa.ifa_fb_sizes = *sizes;
 	ifa.ifa_fb_vaddr = vaddr;

+	/*
+	 * Set helper->fb before config_found(), because
+	 * intelfb_attach_task (scheduled asynchronously from
+	 * config_found -> intelfb_attach) dereferences
+	 * helper->fb->pitches[0].
+	 */
+	ifbdev->helper.fb = &ifbdev->fb->base;
 	/*
 	 * XXX Should do this asynchronously, since we hold
 	 * dev->struct_mutex.
@@ -268,9 +275,9 @@
 	if (helper->fbdev == NULL) {
 		DRM_ERROR("unable to attach intelfb\n");
+		ifbdev->helper.fb = NULL;
 		ret = -ENXIO;
 		goto out_unpin;
 	}
-	ifbdev->helper.fb = &ifbdev->fb->base;
     }
 #else
 	info = drm_fb_helper_alloc_fbi(helper);
--- a/sys/external/bsd/drm2/i915drm/intelfb.c
+++ b/sys/external/bsd/drm2/i915drm/intelfb.c
@@ -127,17 +127,27 @@
 static void
 intelfb_attach_task(struct i915drmkms_task *task)
 {
 	struct intelfb_softc *const sc = container_of(task,
 	    struct intelfb_softc, sc_attach_task);
 	const struct intelfb_attach_args *const ifa = &sc->sc_ifa;
-	const struct drmfb_attach_args da = {
-		.da_dev = sc->sc_dev,
-		.da_fb_helper = ifa->ifa_fb_helper,
-		.da_fb_sizes = &ifa->ifa_fb_sizes,
-		.da_fb_vaddr = ifa->ifa_fb_vaddr,
-		.da_fb_linebytes = ifa->ifa_fb_helper->fb->pitches[0],
-		.da_params = &intelfb_drmfb_params,
-	};
+	struct drm_fb_helper *const helper = ifa->ifa_fb_helper;
+	struct drmfb_attach_args da;
 	int error;

+	if (helper->fb == NULL) {
+		aprint_error_dev(sc->sc_dev,
+		    "no framebuffer allocated, cannot attach drmfb\n");
+		goto out;
+	}
+
+	da = (const struct drmfb_attach_args){
+		.da_dev = sc->sc_dev,
+		.da_fb_helper = helper,
+		.da_fb_sizes = &ifa->ifa_fb_sizes,
+		.da_fb_vaddr = ifa->ifa_fb_vaddr,
+		.da_fb_linebytes = helper->fb->pitches[0],
+		.da_params = &intelfb_drmfb_params,
+	};
+
 	error = drmfb_attach(&sc->sc_drmfb, &da);
 	if (error) {
 		aprint_error_dev(sc->sc_dev, "failed to attach drmfb: %d\n",
@@ -167,6 +177,9 @@
 	struct i915_ggtt *const ggtt = &i915->ggtt;
 	struct i915_vma *const vma = fbdev->vma;

+	if (vma == NULL)
+		return (paddr_t)-1;
+
 	KASSERT(0 <= offset);
 	KASSERT(offset < vma->node.size);

--- a/sys/external/bsd/drm2/drm/drmfb.c
+++ b/sys/external/bsd/drm2/drm/drmfb.c
@@ -95,7 +95,7 @@
 	static const struct genfb_ops zero_genfb_ops;
 	struct genfb_ops genfb_ops = zero_genfb_ops;
 	bool is_console;
-	int error __diagused;
+	int error;

 	/* genfb requires this.  */
 	KASSERTMSG((void *)&sc->sc_genfb == device_private(da->da_dev),
@@ -167,7 +167,11 @@
 	KERNEL_LOCK(1, NULL);
 	error = genfb_attach(&sc->sc_genfb, &genfb_ops);
 	KERNEL_UNLOCK_ONE(NULL);
-	KASSERTMSG(error == 0, "genfb_attach failed, error=%d", error);
+	if (error) {
+		aprint_error_dev(da->da_dev,
+		    "genfb_attach failed, error=%d\n", error);
+		return error;
+	}

 	/* Success!  */
 	return 0;




Home | Main Index | Thread Index | Old Index