LSM Oops 内存错误根因分析与解决


LSM Oops 内存错误根因分析与解决

文章插图

Oops 是 linux 内核中最常见的一种异常出错情况 , 本文通过分析一个具体的 Oops 例子 , 介绍了如何分析并解决该类问题 。
1. LSM 内核模块出现 Oopsinsmod 一个 LSM 的 ko 模块 , 内核打印如下日志:
[415.746844] BUG: unable to handle kernel paging request at ffffffffaa6f0210[415.746846] PGD 3fc0e067 P4D 3fc0e067 PUD 3fc0f063 PMD 34367063 PTE 800000003faf0061[415.746849] Oops: 0003 [#1] SMP PTI[415.746851] CPU: 0 PID: 8366 Comm: insmod Tainted: GOE4.19.82-wwh #1[415.746852] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 07/29/2019[415.746856] RIP: 0010:init_lsm_hooks+0x1cd/0x1f0 [xxx_security][415.746857] Code: 00 00 48 8b 41 10 48 89 71 20 48 8b 10 48 85 d2 75 05 eb 25 48 89 c2 48 8b 02 48 85 c0 75 f5 48 c7 01 00 00 00 00 48 89 51 08 <48> 89 0a 48 83 c1 28 48 39 f9 75 cc 31 c0 c3 48 c7 01 00 00 00 00[415.746858] RSP: 0018:ffffa763c332fcb0 EFLAGS: 00010246[415.746859] RAX: 0000000000000000 RBX: 0000000000000000 RCX: ffffffffc0ae2020[415.746859] RDX: ffffffffaa6f0210 RSI: ffffffffc0adfaa9 RDI: ffffffffc0ae2458[415.746860] RBP: ffffffffc0ada000 R08: 000000000000006d R09: ffffffffaa5c11f3[415.746860] R10: ffffffffaa63c310 R11: ffffffffaa63c6a0 R12: ffffffffc0ae2fc0[415.746861] R13: ffffffffc0ae2e58 R14: ffffa763c332fe98 R15: ffffffffc0ae2e40[415.746862] FS:00007fdf33491200(0000) GS:ffff9ac97bc00000(0000) knlGS:0000000000000000[415.746863] CS:0010 DS: 0000 ES: 0000 CR0: 0000000080050033[415.746863] CR2: ffffffffaa6f0210 CR3: 0000000077222004 CR4: 00000000003606f0[415.746880] Call Trace:[415.746883]init_module+0x34/0xc0 [xxx_security][415.746886]do_one_initcall+0x46/0x1c3[415.746889]? _cond_resched+0x15/0x30[415.746890]? kmem_cache_alloc_trace+0x155/0x1d0[415.746892]do_init_module+0x5a/0x210[415.746894]load_module+0x215b/0x2390[415.746897]? __do_sys_finit_module+0xa8/0x110[415.746898]__do_sys_finit_module+0xa8/0x110[415.746900]do_syscall_64+0x55/0xf0[415.746901]entry_SYSCALL_64_after_hwframe+0x44/0xa9[415.746910] RIP: 0033:0x7fdf335abf592. Oops 访存出错地址分析首先看第一行:
BUG: unable to handle kernel paging request at ffffffffaa6f0210Unable to handle kernel paging request at virtual address 是内存访问异常的错误 , 原因通常有以下三种:
  1. virtual address 为 0x00000000 时 , 说明使用了空指针;
  2. virtual address 没有越出内核地址空间范围 , 说明指针指向的内存受到某种限制;
  3. 除此以外就是指针越出内核地址空间范围;
以上日志中的出错地址 ffffffffaa6f0210 在内核地址空间范围 , 可以判断为试图篡改受限制内存导致报错;例如给一个声明为 const 的变量赋值就会出现这种错误 。
3. 引起 Oops 的内核代码位置初步分析接下来看看日志中的 RIP 信息:
RIP: 0010:init_lsm_hooks+0x1cd/0x1f0 [xxx_security]通常 , 我们在这种情况下从 Oops 收集的最有用的信息是 EIP 和错误的调用地址 。对于 64bit 用户来说 , 你可能需要查看 RIP, EIP/RIP 通常标识了问题发生的现场 。在这个例子中我们可以看到 Oops 中 RIP 是在 init_lsm_hooks 的 0x1cd 字节的位置 , 而 init_lsm_hooks 占用 0x1f0 字节的大小 , 它给出了一个很有用的信息 , 告诉我们去哪里寻找出错的代码 。下文会详细介绍准确找出出错代码的过程 。
而 Oops 日志中的 Code 行 , 会把导致 Oops 的第一条指令 , 也就是 RIP 的值的第一个字节 , 用尖括号 <> 括起来 。如:
Code: 00 00 48 8b 41 10 48 89 71 20 48 8b 10 48 85 d2 75 05 eb 25 48 89 c2 48 8b 02 48 85 c0 75 f5 48 c7 01 00 00 00 00 48 89 51 08 <48> 89 0a 48 83 c1 28 48 39 f9 75 cc 31 c0 c3 48 c7 01 00 00 00 00<48> 即是 。
这种 Code 行 , 在没有自己编译的 vmlinux 时又想定位出错的代码行 , 可以利用 。但是要注意 cpu 的架构问题 , 有些架构的(例如常见的x86)指令是不等长的;
4. Oops Error Code 解析再看看 Error Code 行:
Oops: 0003 [#1] SMP PTI其中 0003 为 error code , 当异常发生时 , 由硬件压入栈中 。可以通过这个看出 Oops 发生的大致原因 。
对于 x86 架构来说 , error code 具体定义如下:
Page fault error code bits:bit 0 == 0: no page found 1: protection faultbit 1 == 0: read access 1: write accessbit 2 == 0: kernel-mode access 1: user-mode accessbit 3 == 1: use of reserved bit detectedbit 4 == 1: fault was an instruction fetch常用低 3 位 , 具体含义为:


推荐阅读