Skip to content

典型故障案例集

案例 1:NULL 指针解引用

现象

BUG: kernel NULL pointer dereference, address: 0000000000000010
RIP: 0010:my_driver_write+0x45/0x120 [my_driver]

根因分析

c
static ssize_t my_driver_write(struct file *filp, ...)
{
    struct my_priv *priv = filp->private_data;
    /* priv 为 NULL,访问 priv->buf(偏移 0x10)崩溃 */
    memcpy(priv->buf, ubuf, len);
}

原因open()filp->private_data 未赋值,或赋值后被意外清零。

修复

c
static int my_driver_open(struct inode *inode, struct file *filp)
{
    struct my_priv *priv = container_of(inode->i_cdev, struct my_priv, cdev);
    filp->private_data = priv;   /* 确保赋值 */

    /* 防御性检查 */
    if (!priv->buf) {
        pr_err("buf not initialized\n");
        return -ENOMEM;
    }
    return 0;
}

案例 2:中断上下文睡眠

现象

BUG: sleeping function called from invalid context at kernel/locking/mutex.c:580
in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 0, name: swapper/0
Call Trace:
 mutex_lock+0x2f/0x50
 my_driver_isr+0x45/0x80 [my_driver]

根因

c
static irqreturn_t my_driver_isr(int irq, void *dev_id)
{
    struct my_priv *priv = dev_id;
    mutex_lock(&priv->lock);   /* ❌ 中断上下文不能睡眠! */
    process_data(priv);
    mutex_unlock(&priv->lock);
    return IRQ_HANDLED;
}

修复

c
static irqreturn_t my_driver_isr(int irq, void *dev_id)
{
    struct my_priv *priv = dev_id;
    unsigned long flags;

    spin_lock_irqsave(&priv->spinlock, flags);   /* ✅ 自旋锁 */
    priv->pending = true;
    spin_unlock_irqrestore(&priv->spinlock, flags);

    schedule_work(&priv->work);   /* 延迟到进程上下文处理 */
    return IRQ_HANDLED;
}

案例 3:use-after-free

现象(KASAN 报告):

BUG: KASAN: use-after-free in my_driver_read+0x67/0x120
Read of size 8 at addr ffff888012345678 by task my_app/1234

Freed by task 5678:
 my_driver_remove+0x45/0x80

根因

c
/* 线程 A:用户进程正在 read() */
static ssize_t my_driver_read(struct file *filp, ...)
{
    struct my_priv *priv = filp->private_data;
    /* 此时线程 B 已经 rmmod,priv 已被 kfree */
    return copy_to_user(buf, priv->data, len);   /* UAF! */
}

/* 线程 B:rmmod */
static int my_driver_remove(struct platform_device *pdev)
{
    struct my_priv *priv = platform_get_drvdata(pdev);
    kfree(priv->data);
    kfree(priv);   /* priv 已释放,但线程 A 还持有指针 */
    return 0;
}

修复:使用引用计数确保 remove 等待所有操作完成:

c
struct my_priv {
    struct kref ref;
    /* ... */
};

static void my_priv_release(struct kref *ref)
{
    struct my_priv *priv = container_of(ref, struct my_priv, ref);
    kfree(priv->data);
    kfree(priv);
}

static int my_driver_open(struct inode *inode, struct file *filp)
{
    struct my_priv *priv = ...;
    kref_get(&priv->ref);   /* 增加引用 */
    filp->private_data = priv;
    return 0;
}

static int my_driver_release(struct inode *inode, struct file *filp)
{
    struct my_priv *priv = filp->private_data;
    kref_put(&priv->ref, my_priv_release);   /* 减少引用,为零时释放 */
    return 0;
}

案例 4:DMA 缓冲区缓存不一致

现象:DMA 传输完成,但 CPU 读取的数据是旧数据(全零或上次的值)。

根因

c
/* ❌ 忘记同步缓存 */
static irqreturn_t dma_complete_isr(int irq, void *dev_id)
{
    struct my_priv *priv = dev_id;
    /* DMA 已将数据写入内存,但 CPU 缓存中还是旧数据 */
    process_data(priv->dma_buf);   /* 读到的是缓存中的旧数据! */
    return IRQ_HANDLED;
}

修复

c
static irqreturn_t dma_complete_isr(int irq, void *dev_id)
{
    struct my_priv *priv = dev_id;

    /* 无效化 CPU 缓存,确保读到 DMA 写入的最新数据 */
    dma_sync_single_for_cpu(priv->dev, priv->dma_addr,
                             priv->dma_size, DMA_FROM_DEVICE);

    process_data(priv->dma_buf);   /* 现在读到的是正确数据 */
    return IRQ_HANDLED;
}

案例 5:probe 返回 -EPROBE_DEFER 死循环

现象:驱动一直打印 probe deferred,设备永远不就绪。

根因:两个驱动互相等待对方先 probe:

driver_A probe → 等待 driver_B 的 GPIO
driver_B probe → 等待 driver_A 的 clock

排查

bash
# 查看 deferred probe 列表
cat /sys/kernel/debug/devices_deferred

修复:检查依赖关系,确保没有循环依赖。通常是设备树描述错误,将 GPIO 控制器和使用者的依赖关系搞反了。


案例 6:内核栈溢出

现象

Kernel stack overflow detected

根因

c
/* ❌ 在栈上分配大数组 */
static int my_driver_ioctl(struct file *filp, unsigned int cmd, ...)
{
    char buf[8192];   /* 内核栈只有 8KB 或 16KB! */
    copy_from_user(buf, (void __user *)arg, 8192);
}

修复

c
/* ✅ 使用堆内存 */
static int my_driver_ioctl(struct file *filp, unsigned int cmd, ...)
{
    char *buf = kmalloc(8192, GFP_KERNEL);
    if (!buf)
        return -ENOMEM;

    if (copy_from_user(buf, (void __user *)arg, 8192)) {
        kfree(buf);
        return -EFAULT;
    }

    /* 处理 */
    kfree(buf);
    return 0;
}

检测工具

bash
# 启用栈溢出检测
CONFIG_VMAP_STACK=y        # 虚拟映射栈(自动检测溢出)
CONFIG_STACK_VALIDATION=y  # 编译时栈深度检查

案例 7:模块卸载时 Oops

现象rmmod 后系统崩溃,崩溃地址在已卸载模块的地址范围内。

根因:定时器或 workqueue 在模块卸载后仍然触发:

c
/* ❌ remove 时未取消定时器 */
static int my_driver_remove(struct platform_device *pdev)
{
    struct my_priv *priv = platform_get_drvdata(pdev);
    /* 忘记 del_timer_sync(&priv->timer) */
    kfree(priv);
    return 0;
}

/* 定时器回调在 kfree 后触发,访问已释放的 priv */
static void my_timer_fn(struct timer_list *t)
{
    struct my_priv *priv = from_timer(priv, t, timer);
    priv->count++;   /* UAF 崩溃! */
}

修复

c
static int my_driver_remove(struct platform_device *pdev)
{
    struct my_priv *priv = platform_get_drvdata(pdev);

    del_timer_sync(&priv->timer);      /* 等待定时器完成并取消 */
    cancel_work_sync(&priv->work);     /* 等待 work 完成并取消 */
    cancel_delayed_work_sync(&priv->dwork);

    kfree(priv);
    return 0;
}

褚成志的笔记