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
0xc0000004b83837e0 6b6b6b6b6b6b6b6b 6b6b6b6b6b6b6b6b kkkkkkkkkkkkkkkk
0xc0000004b83837f0 6b6b6b6b6b6b6b6b 6b6b6b6b6b6b6b6b kkkkkkkkkkkkkkkk
0xc0000004b8383800 6b6b6b6b6b6b6b6b 6b6b6b6b6b6b6b6b kkkkkkkkkkkkkkkk
0xc0000004b8383810 6b6b6b6b6b6b6b6b 6b6b6b6b6b6b6b6b kkkkkkkkkkkkkkkk
0xc0000004b8383820 6b6b6b6b6b6b6b6b 6b6b6b6b6b6b6b6b kkkkkkkkkkkkkkkk
0xc0000004b8383830 6b6b6b6b6b6b6b6b 6b6b6b6b6b6b6b6b kkkkkkkkkkkkkkkk
0xc0000004b8383840 6b6b6b6b6b6b6b6b 6b6b6b6b6b6b6b6b kkkkkkkkkkkkkkkk
0xc0000004b8383850 6b6b6b6b6b6b6b6b 6b6b6b6b6b6b6ba5 kkkkkkkkkkkkkkk.

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

时间: 2024-07-28 13:06:16

linux kernel elv_queue_empty野指针访问内核故障定位与解决的相关文章

Linux Kernel系列 - 牛X的内核代码注释

Hanks.Wang - 专注于操作系统与移动安全研究,Linux-Kernel/SELinux/SEAndroid/TrustZone/Encription/MDM    Mail - [email protected] 牛X的内核代码注释 大牛的代码质量高稳定性好,而且逻辑清晰易读性比较强,今天看到Linux Kernel红黑树的代码时,瞬间被大牛的代码注释秒杀了,看到这样注释的代码真的有阅读的欲望,啥也不说了,上图吧 Linux Kernel系列 - 牛X的内核代码注释

linux /proc目录说明(访问内核数据结构,修改内核设置)

1. /proc目录 Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构.改变内核设置的机制.proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间.它以文件系统的方式为访问系统内核数据的操作提供接口. 用户和应用程序可以通过proc得到系统的信息,并可以改变内核的某些参数.由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取proc文件时,proc文件系统是动态从系统内核读出所需信息并提交的.下面列出的这些文件或子文件夹,并不是都是在你的系

C语言进阶之路(三)----野指针的产生原因及解决办法

1.会产生野指针的做法 #include <stdio.h> //这就是一种错误的写法 int main(){ int *p = NULL; p = (int *)malloc(4); //释放P所指向的内存空间,但指针变量p仍然留在栈中,成为了野指针 if (p != NULL){ free(p); } if (p != NULL){ free(p); } return 0; } 2.正确的做法: #include <stdio.h>//指针变量和指针所指向的内存变量是两个不同的

Linux kernel的中断子系统之(六):ARM中断处理过程

一.前言 本文主要以ARM体系结构下的中断处理为例,讲述整个中断处理过程中的硬件行为和软件动作.具体整个处理过程分成三个步骤来描述: 1.第二章描述了中断处理的准备过程 2.第三章描述了当发生中的时候,ARM硬件的行为 3.第四章描述了ARM的中断进入过程 4.第五章描述了ARM的中断退出过程 本文涉及的代码来自3.14内核.另外,本文注意描述ARM指令集的内容,有些source code为了简短一些,删除了THUMB相关的代码,除此之外,有些debug相关的内容也会删除. 二.中断处理的准备过

【腾讯bugly干货分享】如何定位Obj-C野指针随机Crash(三):加点黑科技让Crash自报家门

本文主要介绍如何利用OC Runtime的特性,让OC野指针对象主动抛出自己的信息,秒杀某些全系统栈Crash. 陈其锋,腾讯SNG即通产品部音视频技术中心软件工程师,主要负责iOS平台音视频功能开发,热衷于移动开发,以及各类APP体验. (注:本文由于涉及一些技术比较猥琐,可能会引起处女座同学的不适,如果有任何疑问欢迎一起讨论.另外,本文只讨论Arm 32位情况) 为什么错误地址是0x55555561? 我们在前文里曾经介绍过在内存释放后填充0x55使野指针出现后数据不能访问,从而使野指针变成

Linux Kernel - Debug Guide (Linux内核调试指南 )

http://blog.csdn.net/blizmax6/article/details/6747601 linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级调试 ***第一部分:基础知识*** 总纲:内核世界的陷阱 源码阅读的陷阱 代码调试的陷阱 原理理解的陷阱 建立调试环境 发行版的选择和安装 安装交叉编译工具 bin工具集的使用 qemu的使用 initrd.img的原理与制作 x86虚拟调试环境的建立 arm虚拟调试环境的建立 arm开发板调试环

linux kernel pwn notes(内核漏洞利用总结)

前言 对这段时间学习的 linux 内核中的一些简单的利用技术做一个记录,如有差错,请见谅. 相关的文件 https://gitee.com/hac425/kernel_ctf 相关引用已在文中进行了标注,如有遗漏,请提醒. 环境搭建 对于 ctf 中的 pwn 一般都是给一个 linux 内核文件 和一个 busybox 文件系统,然后用 qemu 启动起来.而且我觉得用 qemu 调试时 gdb 的反应比较快,也没有一些奇奇怪怪的问题.所以推荐用 qemu 来调,如果是真实漏洞那 vmwar

linux内核可以接受的参数 | Linux kernel启动参数 | 通过grub给内核传递参数

在Linux中,给kernel传递参数以控制其行为总共有三种方法: 1.build kernel之时的各个configuration选项. 2.当kernel启动之时,可以参数在kernel被GRUB或LILO等启动程序调用之时传递给kernel. 3.在kernel运行时,修改/proc或/sys目录下的文件. 这里我简单讲的就是第二种方式了,kernel在grub中配置的启动参数. 首先,kernel有哪些参数呢? 在linux的源代码中,有这样的一个文档Documentation/kern

Linux 内核概述 - Linux Kernel

Linux 内核学习笔记整理. Unix unix 已有40历史,但计算机科学家仍认为其是现存操作系统中最大和最优秀的系统,它已成为一种传奇的存在,历经时间的考验却依然声名不坠. 1973 年,在用 C 语言重写了 Unix 系统后,大量的Unix衍生版本开始出现,许多公司把 unix 移植到新的机型上,开发者们都按照自己的方式不断增强系统的功能.Unix 系统设计简洁并在发布时提供源代码,所以许多团体都对其进行了进一步的开发.加州大学伯克利分校便是其中影响最大的一个.在BSD基础上,很多厂商也