ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PCIe DMA 驱动越界:用 kdump、crash 与 KASAN 复核

PCIe DMA 驱动越界:用 kdump、crash 与 KASAN 复核 PCIe DMA 驱动越界用 kdump、crash 与 KASAN 复核PCIe DMA 驱动出现 Kernel Panic 时可信证据来自对应内核的 vmcore、vmlinux、模块版本和触发输入。下面展示 kdump、crash 与 KASAN 的排查顺序时间戳、并发数和“运行多久必现”都应由实际复现记录提供。结合 Crash 工具解析 Vmcore 提取现场证据如果系统已正确配置 kdump可使用crash配合匹配的vmlinux分析保存下来的内存页。转储范围取决于 kdump 配置不应默认认为包含全部物理内存crash vmlinux /var/crash/dump-id/vmcore进入 crash 交互环境后使用bt查看调用栈和寄存器。输出必须来自当前 vmcore这里只保留命令crash bt -r若调用栈涉及 Slab再根据实际地址查看对应kmem_cachecrash struct kmem_cache address-from-current-dump将寄存器值、异常指令和对象地址放在一起检查。近零指针可能说明指针损坏但还需通过 KASAN、写入路径和触发输入确认是谁破坏了对象。崩溃定位证据链与 Slab 污染溯源流程通过结合kdump静态分析与KASANKernel Address Sanitizer动态检测推导 Slab 内存越界写的物理证据链如果 KASAN 报告指向 SGL 填充函数就继续核对sg_count与数组容量并确认越界写地址是否落入相邻对象。只有地址范围和写入长度吻合才能将它确认为直接原因。带边界校验与 KASAN 友好型的 DMA 驱动修复代码下面代码演示容量检查和部分映射失败时的回滚。MAX_SG_ENTRIES应来自设备与驱动设计不是通用上限#include linux/module.h #include linux/kernel.h #include linux/dma-mapping.h #include linux/slab.h #define MAX_SG_ENTRIES 32 // 硬性约束单次 DMA 最大的 SGL 项数 struct pcie_dma_desc { dma_addr_t dma_phy_addr; uint32_t length; uint32_t flags; }; struct pcie_dma_task { struct pcie_dma_desc sg_list[MAX_SG_ENTRIES]; // 静态固定数组防止越界 uint32_t sg_count; struct dma_pool *pool; }; static struct kmem_cache *g_dma_desc_cache NULL; // 经过防御性重构的描述符分配函数 struct pcie_dma_task* pcie_dma_alloc_task(struct device *dev) { struct pcie_dma_task *task; if (!g_dma_desc_cache) return NULL; // 采用 GFP_KERNEL 标志分配确保在内存紧缺时能适当等待 task kmem_cache_alloc(g_dma_desc_cache, GFP_KERNEL | __GFP_ZERO); if (!task) { dev_err(dev, Failed to allocate dma task from kmem_cache\n); return NULL; } task-sg_count 0; return task; } // 带严格边界校验的 SGL 填充函数 int pcie_dma_fill_sg_list(struct device *dev, struct pcie_dma_task *task, struct page **pages, int page_count) { int i; dma_addr_t dma_handle; // 1. 严格防线校验页数是否超出 SGL 物理数组容量 if (page_count MAX_SG_ENTRIES) { dev_warn(dev, Requested page count %d exceeds MAX_SG_ENTRIES (%d)\n, page_count, MAX_SG_ENTRIES); return -EINVAL; // 拒绝越界请求返回参数错误 } for (i 0; i page_count; i) { // 2. 执行 DMA 单页映射 dma_handle dma_map_page(dev, pages[i], 0, PAGE_SIZE, DMA_BIDIRECTIONAL); if (dma_mapping_error(dev, dma_handle)) { dev_err(dev, DMA mapping failed at page index %d\n, i); // 发生错误时回滚已映射的页防止 DMA 地址泄漏 while (--i 0) { dma_unmap_page(dev, task-sg_list[i].dma_phy_addr, PAGE_SIZE, DMA_BIDIRECTIONAL); } return -EIO; } // 3. 安全写入数组避免任何溢出可能性 task-sg_list[i].dma_phy_addr dma_handle; task-sg_list[i].length PAGE_SIZE; task-sg_list[i].flags 0; } task-sg_count page_count; return 0; }修复后不能只看“没有崩溃”先用原始触发输入复现 KASAN 告警再验证长度上限、映射失败和部分映射等分支。修复后的测试至少保存证据核对内容KASAN / kmemleak是否仍出现越界、释放后使用或泄漏DMA API 调试映射与解除映射是否成对方向是否一致错误返回超限与映射失败是否释放已分配资源并发回归负载、持续时间、内核配置和结果是否完整记录没有这些原始记录就不写“零崩溃”或“无内存泄漏”的结论。每次 Panic 都是改进架构的契机Kernel Panic 不能只靠末尾日志归因。寄存器、vmcore、符号文件和 KASAN 报告需要与触发输入相互印证有些问题还要配合 DMA API 调试或硬件记录。把已证实的边界写入代码和回归用例未确认的部分继续保留为假设。
返回列表