linux kernel elv_queue_empty野指针访问内核故障定位与解决
1. 故障描述
故障操作步骤: 单板上插了一个U盘,出问题前正在通过FTP往单板上拷贝文件,拷贝的过程中单板自动重启。 故障现象: Entering kdb (current=0xc000000594069e38, pid 4) on processor 0 Oops: <NULL> due to oops @ 0xffffffffc08d3d84 [0]more> [0]kdb> |
2. 信息采集
[0]kdb> bt Stack traceback for pid 4 0xc000000594069e38 4 2 1 0 R 0xc00000059406a1a0 *ksoftirqd/0 Stack : fefc4d0e7b71a7af c00000058b4be318 c000000489904298 ffffffffc08d8174 0000000000000001 00000000f0000000 c000000489904258 ffffffffc0950f14 c000000594093c30 c000000594093c30 ffffffffc0e0f298 ffffffffc094ad00 c0000004b9780368 c00000058b5d6f30 c00000058b4be318 c00000058b4be318 fffffffffffffffb 0000000000000000 0000000000000000 c000000561912de8 c00000058b4be318 ffffffffc09534a0 c00000058b5d6f30 c000000561912de8 0000000000040000 ffffffffc09536ac 0000000000000000 c000000244074000 0000000000000000 ffffffffc0dddaa0 0000000000000101 ffffffffc0ddda80 ffffffffc0e71140 0000000000000000 ffffffffc0faf480 ffffffffc0f48c40 ffffffffc0de0000 ffffffffc08df110 c000000594093d20 c000000594093d20 ... Call Trace: [jiffies: 0x1003fe6ae] [<ffffffffc08d3d84>] elv_queue_empty+0x24/0x48 [<ffffffffc08d7ee0>] __blk_run_queue+0x38/0x1d8 [<ffffffffc08d8174>] blk_run_queue+0x2c/0x50 [<ffffffffc0950f14>] scsi_run_queue+0x10c/0x418 [<ffffffffc09534a0>] scsi_next_command+0x48/0x68 [<ffffffffc09536ac>] scsi_io_completion+0x16c/0x550 [<ffffffffc08df110>] blk_done_softirq+0x98/0xb0 [<ffffffffc0679a58>] __do_softirq+0x120/0x1f0 [<ffffffffc0679ba0>] do_softirq+0x78/0x80 [<ffffffffc0679ca0>] ksoftirqd+0xf8/0x250 [<ffffffffc068fe9c>] kthread+0x94/0xa0 [<ffffffffc0638ef0>] kernel_thread_helper+0x10/0x20 <1>CPU 0 Unable to handle kernel paging request at virtual address 6b6b6b6b6b6b6bab, epc == ffffffffc08d3d84, ra == ffffffffc08d7ee0 Cpu 0 $ 0 : 0000000000000000 0000000000000014 6b6b6b6b6b6b6b6b c00000058b4be318 $ 4 : c00000058b4be318 c0000004b83837e0 0000000000000000 c0000004b9780270 $ 8 : 0000000000000004 c0000004b97803b0 0000000000000001 0000000000275c43 $12 : 0000000000000028 ffffffffc0607568 ffffffffc0694b98 1ebdefda014b0000 $16 : c00000058b4be318 c000000489904298 c00000058b4be318 c0000004c6c59a38 $20 : c0000004c6c59a10 0400000000000000 ffffffffffffffbf 0000000000000001 $24 : 0000000000000004 ffffffffc06d7f50 $28 : c000000594090000 c000000594093bf0 ffffffffc0fc0000 ffffffffc08d7ee0 Hi : 0000000000000000 Lo : 0000000000000400 epc : ffffffffc08d3d84 elv_queue_empty+0x24/0x48 Not tainted ra : ffffffffc08d7ee0 __blk_run_queue+0x38/0x1d8 Status: 5400ffe2 KX SX UX KERNEL EXL Cause : 00800008 BadVA : 6b6b6b6b6b6b6bab [0]kdb> md 0xc0000004b83837e0 |
3. 故障分析
反汇编elv_queue_empty函数,计算得出异常指令为
“ffffffffc08d3e98: dca20000 ld v0,0(a1)
ffffffffc08d3e9c: dc590040 ld t9,64(v0)”
对于的C代码段为
“if (e->ops->elevator_queue_empty_fn)”
其中a1(e) = c0000004b83837e0, v0(e->ops) = 6b6b6b6b6b6b6b6b, 0x6b是free poisoning特征值,可以推断request_queue中的elevator字段指向的elevator_queue对象已经被kfree了,但是elevator字段还记录着其地址,导致e->ops为非法值。
从c0000004b83837e0 地址dump的数据特征看,共128字节被use-after-free poisoning 成0x6b,与struct elevator_queue大小一致,也佐证了上面的推断。
因为e对象已被释放,e此时已是野指针,e->ops为非法地址,所以e->ops->elevator_queue_empty_fn, 对非法地址进行寻址操作,从而导致异常。
int elv_queue_empty(struct request_queue *q) { struct elevator_queue *e = q->elevator; if (!list_empty(&q->queue_head)) return 0; if (e->ops->elevator_queue_empty_fn) return e->ops->elevator_queue_empty_fn(q); return 1; } |
ffffffffc08d3e78 <elv_queue_empty>: ffffffffc08d3e78: dc830000 ld v1,0(a0) ffffffffc08d3e7c: dc850018 ld a1,24(a0) ffffffffc08d3e80: 10830005 beq a0,v1,ffffffffc08d3e98 <elv_queue_empty+0x20> ffffffffc08d3e84: 00000000 nop ffffffffc08d3e88: 0000102d move v0,zero ffffffffc08d3e8c: 03e00008 jr ra ffffffffc08d3e90: 00000000 nop ffffffffc08d3e94: 00000000 nop ffffffffc08d3e98: dca20000 ld v0,0(a1) ffffffffc08d3e9c: dc590040 ld t9,64(v0) ffffffffc08d3ea0: 13200003 beqz t9,ffffffffc08d3eb0 <elv_queue_empty+0x38> ffffffffc08d3ea4: 00000000 nop ffffffffc08d3ea8: 03200008 jr t9 ffffffffc08d3eac: 00000000 nop ffffffffc08d3eb0: 24020001 li v0,1 ffffffffc08d3eb4: 03e00008 jr ra ffffffffc08d3eb8: 00000000 nop ffffffffc08d3ebc: 00000000 nop |
4. 解决方案
根据上述分析,得知异常是elevator_queue对象在销毁后,通过野指针访问时产生。
elevator_queue对象是在创建或销毁scsi_host设备对象是进行创建或销毁,其销毁流程的call trace如下:
scsi_host_dev_release -> scsi_free_queue -> blk_cleanup_queue -> elevator_exit -> elevator_release |
反汇编scsi_io_completion函数,因为编译优化的原因,实际走的是scsi_end_request分支,即request queue中的request执行完成后的资源释放流程,和异常时打印的call trace稍有不同,
真实的异常时的call trace如下:
scsi_io_completion -> scsi_end_request -> scsi_next_command -> scsi_run_queue -> blk_run_queue -> __blk_run_queue -> _elv_queue_empty |
综合上述分析,得出结论如下:
1 scsi host dev对象释放时,同时也释放了其关联的request_queue 和elevator_queue等对象,但是却没有及时更新request_queue对象中指向的elevator_queue对象的指针,导致此指针变成野指针;
2 对比2.6.32 LTS版本,发现scsi host dev对象释放时,其下挂的scsi_device对象也被释放,但是却没有及时更新其指向scsi_device对象的指针,即queuedata字段,导致此指针变成野指针。如果及时设置为空指针,scsi_run_queue就知道此scsi_device对象已经不存在,肯定也没有request queue,就可以直接返回,不再继续执行下去,避免后面的异常产生。
针对此两点结论,对以上2种野指针情况进行修改:
1 当释放elevator_queue对象后,及时将request_queue对象中指向的elevator_queue对象的指针设置为空;
2 当释放scsi_device对象后,及时将scsi host dev对象中指向的scsi_device对象的指针设置为空;
5. 参考资料
Make scsi_free_queue() kill pending SCSI commands
Linux Block Device Architecture
linux kernel elv_queue_empty野指针访问内核故障定位与解决的更多相关文章
- linux /proc目录说明(访问内核数据结构,修改内核设置)
1. /proc目录 Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构.改变内核设置的机制.proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间.它以 ...
- C语言进阶之路(三)----野指针的产生原因及解决办法
1.会产生野指针的做法 #include <stdio.h> //这就是一种错误的写法 int main(){ int *p = NULL; p = (); //释放P所指向的内存空间,但 ...
- Linux kernel的中断子系统之(六):ARM中断处理过程
返回目录:<ARM-Linux中断系统>. 总结:二中断处理经过两种模式:IRQ模式和SVC模式,这两种模式都有自己的stack,同时涉及到异常向量表中的中断向量. 三ARM处理器在感知到 ...
- Linux kernel workqueue机制分析
Linux kernel workqueue机制分析 在内核编程中,workqueue机制是最常用的异步处理方式.本文主要基于linux kernel 3.10.108的workqueue文档分析其基 ...
- [中英对照]Linux kernel coding style | Linux内核编码风格
Linux kernel coding style | Linux内核编码风格 This is a short document describing the preferred coding sty ...
- Linux kernel pwn notes(内核漏洞利用学习)
前言 对这段时间学习的 linux 内核中的一些简单的利用技术做一个记录,如有差错,请见谅. 相关的文件 https://gitee.com/hac425/kernel_ctf 相关引用已在文中进行了 ...
- Linux Kernel - Debug Guide (Linux内核调试指南 )
http://blog.csdn.net/blizmax6/article/details/6747601 linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级 ...
- 深入linux kernel内核配置选项
============================================================================== 深入linux kernel内核配置选项 ...
- Linux 内核概述 - Linux Kernel
Linux 内核学习笔记整理. Unix unix 已有40历史,但计算机科学家仍认为其是现存操作系统中最大和最优秀的系统,它已成为一种传奇的存在,历经时间的考验却依然声名不坠. 1973 年,在用 ...
随机推荐
- java基础 绘图技术.坦克大战 之java绘图坐标体系(一)
坐标体系介绍 下图说明了java坐标体系.坐标原点位于左上角,以像素为单位,像素是计算机屏幕上最小的显示单位.在java的坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素:第二个 ...
- Xcode will continue when iPad is finished. "Could not find Developer Disk Image"
1: Xcode will continue when iPad is finished. 等待进度条读取完成即可: 2: xcode,安装新版本的iOS 的 xcode 支持文件 的路径: /ap ...
- Git的checkout, reset, revert
不管是修改还是新建文件,都必须通过git add把这次修改从工作区加到暂存区: commit只是提交暂存区的修改,还没add到暂存区处于工作区的修改是不会commit的: git checkout ...
- 《PHP字符串函数》笔记
① str_repeat() 重复一个字符串; ② str_shuffle() 随机打乱一个字符串; ③ strftime() 根据区域设置格式化本地时间/日期; ④ strip_tags() 从字符 ...
- 高性能MySQL(五):查询性能优化
当向MySQL 发送一个请求的时候MySQL 到底做了什么? 1.客户端发送一条查询给服务器 2.服务器先检查查询缓存,如果命中了缓存,则立即返回存储在缓存中的结果.否则进入下一阶段 3.服务器端进行 ...
- JQuery实现无刷新下拉加载图片
最近做的一个项目需要做页面无刷新下拉加载图片,调研了一番,大多都采用检测滚动条达到底部,然后利用ajax加载下一页数据对页面数据进行添加,根据这一逻辑,自己写了一个,具体代码如下: JQu ...
- UIRefreshControl
在iOS6中UITableViewController 已经集成了UIRefreshControl 控件.UIRefreshControl目前只能用于UITableViewController
- JavaScript 操作 Cookie
转自作者:聂微东出处:http://www.cnblogs.com/Darren_code/ 什么是 Cookie “cookie 是存储于访问者的计算机中的变量.每当同一台计算机通过浏览器 ...
- WPF 如何绘制不规则按钮,并且有效点击范围也是不规则的
最近在做一个东西,如地图,点击地图上的某一区域,这一区域需要填充成其他颜色.区域是不规则的,而且点击该区域的任一点,都能够变色.普通的按钮只是简单的加载一幅图肯定是不行的.查了很多资料,终于把它搞定了 ...
- css flex布局
关于flex布局的一些简单用法 效果(下图) 实现代码: <!--html--> <div class="wrap"> <div class=&quo ...