Dear Maintainers,
When using our customized Syzkaller to fuzz the latest Linux kernel, the 
following crash was triggered.
HEAD commit:7d0a66e4bb9081d75c82ec4957c50034cb0ea449
git tree: upstream
Output:https://github.com/manual0/crash/blob/main/report1.txt
Kernel config: https://github.com/manual0/crash/blob/main/config.txt
C reproducer:https://github.com/manual0/crash/blob/main/repro1.c

drm_gem_object_handle_put_unlocked within drivers/gpu/drm/drm_gem.c which was 
identified during fuzzing on a Linux 6.18 kernel. This warning indicates a 
reference counting inconsistency when releasing a GEM object handle during the 
process exit path.The issue consistently occurs during the resource cleanup 
sequence where drm_release calls drm_file_free and subsequently 
drm_gem_release, which then iterates through GEM handles via idr_for_each. We 
noticed that syzbot has previously reported a similar warning in 
drm_gem_object_handle_put_unlocked under extid ef3256a360c02207a4cb, but our 
finding is distinct because the syzbot report is triggered during the creation 
path via drm_mode_create_dumb_ioctl whereas our trace proves the issue persists 
in the cleanup path even on the newer 6.18.0 upstream tree. We have searched 
for existing patches but found none that address this specific release-side 
inconsistency. This bug was reproduced in a specialized environment using a 
custom-modeled device added to QEMU to simulate specific hardware-driver 
interactions. To assist in your analysis, we have provided our QEMU device 
modeling file in the appendix as the bug may not be triggerable on standard 
emulated hardware. 

If you fix this issue, please add the following tag to the commit:
Reported-by: Zhi Wang <[email protected]>, Bin Yu<[email protected]>, 
MingYu Wang<[email protected]>, WenJian Lu<[email protected]>, KeFeng 
Gao<[email protected]>, thank you!

WARNING: CPU: 1 PID: 108806 at drivers/gpu/drm/drm_gem.c:300 
drm_gem_object_handle_put_unlocked+0x30e/0x3e0
Modules linked in:
CPU: 1 UID: 0 PID: 108806 Comm: syz.1.31978 Not tainted 6.18.0 #1 PREEMPT(full) 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 
rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org 04/01/2014
RIP: 0010:drm_gem_object_handle_put_unlocked+0x30e/0x3e0
Code: ed 74 61 e8 74 a8 84 fc 4c 89 e7 e8 2c 9e 94 00 48 c7 c1 60 09 ea 8b 4c 
89 ea 48 c7 c7 40 05 ea 8b 48 89 c6 e8 e3 3b 44 fc 90 <0f> 0b 90 90 5b 5d 41 5c 
41 5d 41 5e 41 5f e9 3f a8 84 fc 4c 89 ff
RSP: 0018:ffffc90002d27b80 EFLAGS: 00010286
RAX: 0000000000000000 RBX: ffff88811ac0fbe0 RCX: ffffffff8179ed09
RDX: ffff888124a2ba00 RSI: ffffffff8179ed16 RDI: 0000000000000001
RBP: ffff888107df1000 R08: 0000000000000001 R09: ffffed1026bc4841
R10: 0000000000000001 R11: 0000000000139fc0 R12: ffff8881001ec0c8
R13: ffff88801c71bd60 R14: 0000000000000000 R15: ffff888107df1004
FS:  000055557ced1500(0000) GS:ffff8881a2601000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007ffe5646ff00 CR3: 000000012fba2000 CR4: 00000000000006f0
Call Trace:
 <TASK>
 drm_gem_object_release_handle+0xc7/0x200
 idr_for_each+0x119/0x230 home/wmy/Fuzzer/third_tool/linux-6.18/lib/idr.c:208
 drm_gem_release+0x29/0x40
 drm_file_free.part.0+0x724/0xcf0
 drm_close_helper.isra.0+0x183/0x1f0
 drm_release+0x1ab/0x360
 __fput+0x402/0xb50 home/wmy/Fuzzer/third_tool/linux-6.18/fs/file_table.c:468
 task_work_run+0x16b/0x260 
home/wmy/Fuzzer/third_tool/linux-6.18/kernel/task_work.c:227
 exit_to_user_mode_loop+0xf9/0x130
 do_syscall_64+0x424/0xfa0 
home/wmy/Fuzzer/third_tool/linux-6.18/arch/x86/entry/syscall_32.c:308
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f5f9d3b059d
Code: 02 b8 ff ff ff ff c3 66 0f 1f 44 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 
89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 
c3 48 c7 c1 a8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffeb85fc6d8 EFLAGS: 00000246 ORIG_RAX: 00000000000001b4
RAX: 0000000000000000 RBX: 00007f5f9d627da0 RCX: 00007f5f9d3b059d
RDX: 0000000000000000 RSI: 000000000000001e RDI: 0000000000000003
RBP: 00007ffeb85fc778 R08: 0000001b3292016a R09: 0000000000000000
R10: 0000001b32d20000 R11: 0000000000000246 R12: ffffffffffffffff
R13: 00007f5f9d62609c R14: 00007f5f9d627da0 R15: 00007ffeb85fc7a0
 </TASK>

Thanks,
Zhi Wang
/*
 * QEMU Bochs DRM-compatible PCI device
 *
 * Implements the minimal Bochs/QEMU stdvga "dispi" interface and MMIO
 * layout needed by the Linux drivers/gpu/drm/tiny/bochs.c driver.
 *
 * Target QEMU: 8.2.10
 */

#include "qemu/osdep.h"
#include <inttypes.h>
#include <string.h>
#include "qemu/module.h"
#include "qemu/log.h"
#include "qapi/error.h"
#include "exec/memory.h"
#include "sysemu/dma.h"
#include "hw/irq.h"
#include "hw/pci/pci.h"
#include "hw/pci/msi.h"
#include "hw/pci/msix.h"
#include "hw/pci/pcie.h"
#include "qom/object.h"
#include "qapi/visitor.h"
#include "hw/qdev-properties.h"

#define TYPE_PCIBASE_DEVICE "bochs_drm_pci"
typedef struct PCIBaseState PCIBaseState;
OBJECT_DECLARE_SIMPLE_TYPE(PCIBaseState, PCIBASE_DEVICE)

#define VBE_DISPI_INDEX_ID               0x0
#define VBE_DISPI_INDEX_XRES             0x1
#define VBE_DISPI_INDEX_YRES             0x2
#define VBE_DISPI_INDEX_BPP              0x3
#define VBE_DISPI_INDEX_ENABLE           0x4
#define VBE_DISPI_INDEX_BANK             0x5
#define VBE_DISPI_INDEX_VIRT_WIDTH       0x6
#define VBE_DISPI_INDEX_VIRT_HEIGHT      0x7
#define VBE_DISPI_INDEX_X_OFFSET         0x8
#define VBE_DISPI_INDEX_Y_OFFSET         0x9
#define VBE_DISPI_INDEX_VIDEO_MEMORY_64K 0xa
#define VBE_DISPI_ID0                    0xB0C0
#define VBE_DISPI_ID1                    0xB0C1
#define VBE_DISPI_ID2                    0xB0C2
#define VBE_DISPI_ID3                    0xB0C3
#define VBE_DISPI_ID4                    0xB0C4
#define VBE_DISPI_ID5                    0xB0C5
#define VBE_DISPI_DISABLED               0x00
#define VBE_DISPI_ENABLED                0x01
#define VBE_DISPI_GETCAPS                0x02
#define VBE_DISPI_8BIT_DAC               0x20
#define VBE_DISPI_LFB_ENABLED            0x40
#define VBE_DISPI_NOCLEARMEM             0x80
#define VBE_DISPI_IOPORT_INDEX           0x01CE
#define VBE_DISPI_IOPORT_DATA            0x01CF
#define BOCHS_VENDOR_ID                  0x1234
#define BOCHS_DEVICE_ID                  0x1111

/* BAR metadata */
typedef enum {
    BAR_TYPE_NONE = 0,
    BAR_TYPE_MMIO,
    BAR_TYPE_PIO,
    BAR_TYPE_RAM
} BARType;

typedef struct {
    int index;
    BARType type;
    hwaddr size;
    const char *name;
    bool sparse;
} BARInfo;

struct PCIBaseState {
    PCIDevice parent_obj;

    MemoryRegion bar_regions[6];

    uint8_t *mmio_backing;
    size_t mmio_backing_size;

    BARInfo bar_info[6];
    int num_bars;

    bool has_msi;
    bool has_msix;

    /* Dispi/VGA emulation state */
    uint16_t dispi_index;
    uint16_t dispi_regs[16];

    /* Simple frame buffer backing for BAR0 (VRAM) */
    MemoryRegion vram_region;
    hwaddr vram_size;

    /* MMIO BAR2 size cached */
    hwaddr mmio_size;
};

static uint64_t pcibase_mmio_read(void *opaque, hwaddr addr, unsigned size);
static void pcibase_mmio_write(void *opaque, hwaddr addr, uint64_t val, 
unsigned size);
static uint64_t pcibase_pio_read(void *opaque, hwaddr addr, unsigned size);
static void pcibase_pio_write(void *opaque, hwaddr addr, uint64_t val, unsigned 
size);
static uint32_t pcibase_config_read(PCIDevice *pdev, uint32_t addr, int len);
static void pcibase_config_write(PCIDevice *pdev, uint32_t addr, uint32_t val, 
int len);
static void pcibase_reset(DeviceState *dev);
static void pcibase_realize(PCIDevice *pdev, Error **errp);
static void pcibase_uninit(PCIDevice *pdev);

static const MemoryRegionOps pcibase_mmio_ops = {
    .read = pcibase_mmio_read,
    .write = pcibase_mmio_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
    .valid = { .min_access_size = 1, .max_access_size = 4 },
    .impl  = { .min_access_size = 1, .max_access_size = 4 },
};

static const MemoryRegionOps pcibase_pio_ops = {
    .read = pcibase_pio_read,
    .write = pcibase_pio_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
    .valid = { .min_access_size = 1, .max_access_size = 4 },
    .impl  = { .min_access_size = 1, .max_access_size = 4 },
};

static void pcibase_register_bar(PCIDevice *pdev, PCIBaseState *s, BARInfo *bi, 
Error **errp)
{
    if (!bi || bi->type == BAR_TYPE_NONE) {
        return;
    }

    MemoryRegion *mr = &s->bar_regions[bi->index];

    if (bi->type == BAR_TYPE_MMIO) {
        memory_region_init_io(mr, OBJECT(s), &pcibase_mmio_ops, s, bi->name, 
bi->size);
        pci_register_bar(pdev, bi->index, PCI_BASE_ADDRESS_SPACE_MEMORY, mr);
    } else if (bi->type == BAR_TYPE_PIO) {
        memory_region_init_io(mr, OBJECT(s), &pcibase_pio_ops, s, bi->name, 
bi->size);
        pci_register_bar(pdev, bi->index, PCI_BASE_ADDRESS_SPACE_IO, mr);
    } else if (bi->type == BAR_TYPE_RAM) {
        memory_region_init_ram(mr, OBJECT(s), bi->name, bi->size, errp);
        pci_register_bar(pdev, bi->index, PCI_BASE_ADDRESS_SPACE_MEMORY, mr);
    }
}

/* MMIO BAR2 layout implemented from driver usage:
 *  0x000-0x3ff : EDID buffer (bochs_get_edid_block)
 *  0x400-0x43f : VGA ports 0x3c0-0x3df mapped as bytes
 *  0x500-0x51f : dispi registers, 16-bit each: offset = 0x500 + (reg<<1)
 *  0x600       : qext_size (readl)
 *  0x604       : endian control (writel 0xbebebebe / 0x1e1e1e1e)
 */

static uint64_t pcibase_mmio_read(void *opaque, hwaddr addr, unsigned size)
{
    PCIBaseState *s = opaque;

    if (addr < 0x400) {
        /* EDID and other data; driver only reads bytes */
        if (size == 1 && s->mmio_backing && addr < s->mmio_backing_size) {
            return s->mmio_backing[addr];
        }
        return 0;
    }

    if (addr >= 0x400 && addr < 0x440) {
        /* VGA MMIO window for ports 0x3c0-0x3df. Driver uses only 
bochs_vga_readb */
        if (size == 1) {
            return 0;
        }
        return 0;
    }

    if (addr >= 0x500 && addr < 0x520) {
        /* dispi registers as 16-bit */
        if (size != 2) {
            return 0;
        }
        uint16_t reg = (addr - 0x500) >> 1;
        if (reg >= 16) {
            return 0;
        }
        return s->dispi_regs[reg];
    }

    if (addr == 0x600 && size == 4) {
        /* qext_size field */
        return s->mmio_size;
    }

    if (addr == 0x604 && size == 4) {
        /* endian control readback: keep as zero; driver only checks write 
pattern */
        return 0;
    }

    return 0;
}

static void pcibase_mmio_write(void *opaque, hwaddr addr, uint64_t val, 
unsigned size)
{
    PCIBaseState *s = opaque;

    if (addr < 0x400) {
        /* EDID / other read-only for our purposes; driver only reads */
        return;
    }

    if (addr >= 0x400 && addr < 0x440) {
        /* VGA MMIO window for ports 0x3c0-0x3df; driver uses writeb */
        if (size == 1) {
            if (s->mmio_backing && addr < s->mmio_backing_size) {
                s->mmio_backing[addr] = (uint8_t)val;
            }
        }
        return;
    }

    if (addr >= 0x500 && addr < 0x520) {
        if (size != 2) {
            return;
        }
        uint16_t reg = (addr - 0x500) >> 1;
        if (reg >= 16) {
            return;
        }
        uint16_t v = (uint16_t)val;
        s->dispi_regs[reg] = v;
        return;
    }

    if (addr == 0x600 && size == 4) {
        /* qext_size is read-only for driver; ignore writes */
        return;
    }

    if (addr == 0x604 && size == 4) {
        /* endian control: driver writes markers only; no other effect 
implemented */
        return;
    }
}

/* Bochs driver may fall back to I/O ports if MMIO BAR2 is not present.
 * For simplicity we emulate only the subset of ports used by the driver.
 */
static uint64_t pcibase_pio_read(void *opaque, hwaddr addr, unsigned size)
{
    PCIBaseState *s = opaque;

    if (addr == (VBE_DISPI_IOPORT_INDEX & 0xfff) && size == 2) {
        return s->dispi_index;
    }
    if (addr == (VBE_DISPI_IOPORT_DATA & 0xfff) && size == 2) {
        uint16_t reg = s->dispi_index;
        if (reg < 16) {
            return s->dispi_regs[reg];
        }
        return 0;
    }

    return 0;
}

static void pcibase_pio_write(void *opaque, hwaddr addr, uint64_t val, unsigned 
size)
{
    PCIBaseState *s = opaque;

    if (addr == (VBE_DISPI_IOPORT_INDEX & 0xfff) && size == 2) {
        s->dispi_index = (uint16_t)val;
        return;
    }
    if (addr == (VBE_DISPI_IOPORT_DATA & 0xfff) && size == 2) {
        uint16_t reg = s->dispi_index;
        if (reg < 16) {
            s->dispi_regs[reg] = (uint16_t)val;
        }
        return;
    }
}

static void pcibase_reset(DeviceState *dev)
{
    PCIBaseState *s = PCIBASE_DEVICE(dev);
    PCIDevice *pdev = PCI_DEVICE(dev);

    pci_device_reset(pdev);

    if (s->mmio_backing && s->mmio_backing_size) {
        memset(s->mmio_backing, 0, s->mmio_backing_size);
    }

    memset(s->dispi_regs, 0, sizeof(s->dispi_regs));
    s->dispi_index = 0;

    /* Initialize ID and video memory size */
    s->dispi_regs[VBE_DISPI_INDEX_ID] = VBE_DISPI_ID5;
    if (s->vram_size) {
        s->dispi_regs[VBE_DISPI_INDEX_VIDEO_MEMORY_64K] = 
(uint16_t)(s->vram_size / (64 * 1024));
    }
}

static void pcibase_dma_device_realize(PCIDevice *pdev, Error **errp)
{
    (void)pdev;
    (void)errp;
}

static uint32_t pcibase_config_read(PCIDevice *pdev, uint32_t addr, int len)
{
    uint32_t val = pci_default_read_config(pdev, addr, len);

    switch (len) {
    case 1:
        val &= 0xFF;
        break;
    case 2:
        val &= 0xFFFF;
        break;
    case 4:
    default:
        break;
    }
    return val;
}

static void pcibase_config_write(PCIDevice *pdev, uint32_t addr, uint32_t val, 
int len)
{
    pci_default_write_config(pdev, addr, val, len);
}

static void pcibase_realize(PCIDevice *pdev, Error **errp)
{
    PCIBaseState *s = PCIBASE_DEVICE(pdev);
    uint8_t *pci_conf = pdev->config;

    s->num_bars = 0;

    /* Set up VRAM (BAR0): 16 MiB */
    s->vram_size = 16 * 1024 * 1024;
    s->mmio_backing_size = s->vram_size;
    s->mmio_backing = g_malloc0(s->mmio_backing_size);

    memory_region_init_ram(&s->vram_region, OBJECT(s), "bochs-vram", 
s->vram_size, errp);
    pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->vram_region);

    /* MMIO BAR2: map MMIO space used by driver (EDID, VGA, dispi, qext) */
    s->mmio_size = 0x1000;
    s->bar_info[s->num_bars].index = 2;
    s->bar_info[s->num_bars].type = BAR_TYPE_MMIO;
    s->bar_info[s->num_bars].size = s->mmio_size;
    s->bar_info[s->num_bars].name = "bochs-mmio";
    s->bar_info[s->num_bars].sparse = false;
    s->num_bars++;

    pci_set_word(pci_conf + PCI_VENDOR_ID,  BOCHS_VENDOR_ID);
    pci_set_word(pci_conf + PCI_DEVICE_ID,  BOCHS_DEVICE_ID);
    pci_set_word(pci_conf + PCI_CLASS_DEVICE, PCI_CLASS_DISPLAY_OTHER);
    pci_set_byte(pci_conf + PCI_REVISION_ID, 2); /* >= 2 so qext is used */
    pci_config_set_interrupt_pin(pci_conf, 1);

    for (int i = 0; i < s->num_bars; i++) {
        pcibase_register_bar(pdev, s, &s->bar_info[i], errp);
    }

    pcibase_dma_device_realize(pdev, errp);
    if (errp && *errp) {
        return;
    }

    pcibase_reset(DEVICE(pdev));
}

static void pcibase_uninit(PCIDevice *pdev)
{
    PCIBaseState *s = PCIBASE_DEVICE(pdev);

    if (s->has_msix) {
        msix_uninit(pdev, NULL, 0);
        s->has_msix = false;
    }
    if (s->has_msi) {
        msi_uninit(pdev);
        s->has_msi = false;
    }

    if (s->mmio_backing) {
        g_free(s->mmio_backing);
        s->mmio_backing = NULL;
    }
}

static void pcibase_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);

    k->config_read  = pcibase_config_read;
    k->config_write = pcibase_config_write;

    k->realize = pcibase_realize;
    k->exit    = pcibase_uninit;
    dc->reset  = pcibase_reset;

    set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
}

static void pcibase_register_types(void)
{
    static InterfaceInfo interfaces[] = {
        { INTERFACE_CONVENTIONAL_PCI_DEVICE },
        { },
    };

    static const TypeInfo pcibase_info = {
        .name = TYPE_PCIBASE_DEVICE,
        .parent = TYPE_PCI_DEVICE,
        .instance_size = sizeof(PCIBaseState),
        .class_init = pcibase_class_init,
        .interfaces = interfaces,
    };

    type_register_static(&pcibase_info);
}

type_init(pcibase_register_types);

Reply via email to