ipu7: Double-Free and Use-After-Free in Error Paths (CVE-2026-64447)

A double-free and use-after-free in the ipu7 staging driver. Both ipu7_isys_init() and ipu7_psys_init() allocate a pdata struct, pass it to ipu7_bus_initialize_device() which stores it in the device and registers a release callback that frees it. On two distinct error paths, the callers then call kfree(pdata) a second time — and on one of them, also dereference the device struct after put_device() has freed it.

1. Background

The ipu7 staging driver was merged in commit b7fe4c0019b1 to support the camera subsystem in Intel Meteor Lake and Lunar Lake processors — drivers/staging/media/ipu7/ in the kernel tree, landed while the upstream API was still under review.

I found both bugs while reading through ipu7_isys_init() and ipu7_psys_init() in ipu7.c — the driver had just been merged to staging and the auxiliary bus lifetime rules are subtle, so I went through the error paths to check whether pdata ownership was being respected after ipu7_bus_initialize_device() stored it in the device struct. Both functions allocate pdata with kzalloc(), pass it to the device initializer which stores the pointer and registers a release callback that frees it — and then, on two separate error paths, call kfree(pdata) themselves. The device model had already freed it.

2. Who owns pdata

ipu7_bus_initialize_device() takes the caller-allocated pdata pointer and stores it inside the new device struct:

adev->pdata = pdata;
...
auxdev->dev.release = ipu7_bus_release;

After auxiliary_device_init() succeeds, the device model owns the object. From that point on, put_device(&adev->auxdev.dev) — called whenever the reference count hits zero — triggers the release callback:

static void ipu7_bus_release(struct device *dev)
{
    struct ipu7_bus_device *adev = to_ipu7_bus_device(dev);

    kfree(adev->pdata);
    kfree(adev);
}

ipu7_bus_release() frees both pdata and the device struct itself. A caller that calls put_device() must not touch either pointer afterward.

3. Bug 1 — double-free + UAF on mmu_init failure

After the auxiliary device is initialized, both init functions set up the MMU for the sub-device. If that fails, the old error path was:

/* ipu7.c — before the fix (ipu7_isys_init) */
if (IS_ERR(isys_adev->mmu)) {
    dev_err_probe(dev, PTR_ERR(isys_adev->mmu),
                  "ipu7_mmu_init(isys_adev->mmu) failed\n");
    put_device(&isys_adev->auxdev.dev);   /* → ipu7_bus_release:
                                               kfree(isys_adev->pdata)  ← pdata freed
                                               kfree(isys_adev)         ← adev freed */
    kfree(pdata);                          /* ← double-free: pdata already freed above */
    return ERR_CAST(isys_adev->mmu);       /* ← UAF: isys_adev is freed */
}

put_device() drops the refcount to zero, which calls ipu7_bus_release(). That function frees both isys_adev->pdata (the same pointer as pdata in the caller) and isys_adev itself. The subsequent kfree(pdata) is a double-free. The ERR_CAST(isys_adev->mmu) on the next line reads a field from memory that has just been freed — a use-after-free.

The same pattern appears verbatim in ipu7_psys_init() for the PSYS sub-device.

4. Bug 2 — double-free on bus_add_device failure

Both functions have a second error path when ipu7_bus_add_device() fails. Inside that function, on failure, auxiliary_device_uninit() is called, which calls put_device() — again triggering ipu7_bus_release() and freeing pdata. The old callers then did this:

ret = ipu7_bus_add_device(isys_adev);
if (ret) {
    kfree(pdata);          /* ← double-free: already freed inside ipu7_bus_add_device */
    return ERR_PTR(ret);
}

Again, the same in both functions.

5. Affected hardware

The ipu7 driver covers Intel Meteor Lake (Core Ultra Series 1) and Lunar Lake (Core Ultra Series 2) — the platforms that introduced IPU7. Older Intel platforms use the ipu6 driver and are not affected. The bug is in the initialization path, which runs when the module loads at boot or on first use on the affected hardware.

6. The fix

For Bug 1, the fix saves the error code before calling put_device(), while isys_adev is still valid, then returns it without touching the freed pointer:

 if (IS_ERR(isys_adev->mmu)) {
-    dev_err_probe(dev, PTR_ERR(isys_adev->mmu),
-                  "ipu7_mmu_init(isys_adev->mmu) failed\n");
+    ret = dev_err_probe(dev, PTR_ERR(isys_adev->mmu),
+                        "ipu7_mmu_init(isys_adev->mmu) failed\n");
     put_device(&isys_adev->auxdev.dev);
-    kfree(pdata);
-    return ERR_CAST(isys_adev->mmu);
+    return ERR_PTR(ret);
 }

For Bug 2, the fix removes the manual kfree(pdata) from the caller, since ipu7_bus_add_device() already cleans up through the release callback:

 ret = ipu7_bus_add_device(isys_adev);
-if (ret) {
-    kfree(pdata);
+if (ret)
     return ERR_PTR(ret);
-}

Both changes apply identically to ipu7_isys_init() and ipu7_psys_init(). The patch was reviewed by Dan Carpenter and signed off by Sakari Ailus (Intel, ipu7 maintainer).

7. Patch status

The fix was submitted to the staging tree on 2026-04-13 and merged as upstream commit d3a9a8cf2d7f with Fixes: b7fe4c0019b1. The ipu7 driver does not exist in LTS kernels older than 6.18, so stable backports went to two trees only:

  • 7.1.4 (released 2026-07-18) — commit 837c1f96
  • 6.18.39 (released 2026-07-18) — commit b5ddc725

CVE-2026-64447 was assigned by the kernel security team (NVD, CVSS 7.8 High).

The bug is in the driver initialization path — triggered when the ipu7 module loads on Meteor Lake or Lunar Lake hardware. The fix is present in 7.1.4 and 6.18.39 and all later releases.

Other recent kernel work: a use-after-free in the nvec staging driver (separate post), a heap overflow in the iSCSI target CHAP code (separate post), patch series for the rtl8723bs WiFi driver (separate post), an OOB array access in the mac80211 WiFi stack (separate post), and a heap OOB read in the gdk-pixbuf ICNS loader (separate post).