1. 执行摘要
本次分析针对一起 Qualcomm Ravelin SNP-AN00 平台上的 kernel panic 进行根因定位。
根因判定: EROFS 压缩文件系统在进行 LZ4 解压缩过程中,z_erofs_lz4_decompress_partial 函数通过 __memcpy 读取压缩源页面时,触发了 kernel 级别的 translation fault。直接原因是源页面和目标页面均已被 page allocator 释放(POISON_FREE 模式 dead000000000400),导致 use-after-free。
竞争路径: 内核压缩内存回收线程 kcompactd0(PID 85, CPU 4)在 crash 前约 544ms 执行了 erofs: managed_cache_releasepage,累计回收了 51355 个页面。EROFS readahead 路径(CPU 7)在解压缩过程中访问了已被回收的页面,形成经典的 page reclaim race。
关于 bitflip 怀疑: 已排除。故障地址 ffffffc054099984 是有效的 kernel linear map 地址,PTE 为零是因为物理页面已被释放而非地址损坏。寄存器中未发现单比特偏差模式。dead000000000400 是内核标准的页面释放毒化值。
置信度: high-probability
2. 分析产物
3. 会话模式与工具约束
Split dump 模式:使用
build_dump_spec和extract_qcom_kaslr构建启动参数drgn 不可用:无法使用 drgn 脚本进行高级内存分析(如直接检查
struct page字段、遍历 VMA 链表等)Crash 命令行为异常:在第一次会话中,部分 crash 命令(如
sys、mod、log)返回了不相关的输出(如kmem -s、kmem -n等),疑似 crash binary 的命令映射异常。关闭并重新打开会话后恢复正常bt -l/bt -f限制:对于 panic 上下文的任务,crash 无法展开完整回溯栈,仅返回任务头信息kmem对 NULL PTE 地址的限制:kmem <faulting_vaddr>返回 "cannot make virtual-to-physical translation",因为 PTE 为零。需要通过kmem -P <phys_addr>使用物理地址查询
4. 调查过程与命令推导
4.1 Intake 阶段
可用信息: 用户提供了 split ramdump 路径、vmlinux、platform=qcom、vabits_actual=39,并怀疑可能存在 bitflip。
执行步骤:
detect_dump_layout→ 确认 split dump, Qualcomm 平台, 6 个 DDRCS 文件, OCIMEM 存在check_symbol_readiness→ vmlinux 就绪,模块符号未提供validate_ramdump_inputs→ ready=true,自动推导 dump_spec 和 crash_argsbuild_dump_spec→ 生成 6 段 dump_specextract_qcom_kaslr→ 从 OCIMEM.BIN 提取 KASLR=0x252b200000open_vmcore_session→ 会话 ID: 995b9abe
4.2 第一轮命令:bt + log
开放问题: crash 的直接原因是什么?调用栈是什么?
执行: bt 和 log | tail -n 300(并行)
结果:
bt仅返回任务头:PID 5432, CPU 7, "e.hwvoipservice"log包含完整的 panic 调用栈和多个 CPU 的 stopping 信息
关键发现: panic 调用栈显示:
__memcpy+0xac/0x180 ← 故障指令
lz4_decompress+0x18c/0x424
decompress_generic+0x294/0x3a4
z_erofs_vle_unzip+0x5c0/0xa18
z_erofs_submit_and_unzip+0x1e8/0x24c
z_erofs_vle_normalaccess_readpages+0x338/0x47c
read_pages -> page_cache_ra_unbounded -> do_sync_mmap_readahead
filemap_fault -> __do_fault -> do_read_fault
handle_pte_fault -> handle_mm_fault -> do_page_fault
do_translation_fault -> do_mem_abort -> el0_da -> el0_sync
假设更新: 这是一个 EROFS LZ4 解压缩过程中的 translation fault。故障发生在 kernel 模式(el1_abort),由用户空间页面错误(el0_da)触发的 readahead 路径引起。
4.3 定位 Oops 寄存器转储
开放问题: 故障地址是什么?寄存器状态如何?
推导: bt -f 和 bt -l 无法展开栈。log 的最后 300 行不包含 Oops 头部。需要搜索 "Unable to handle" 消息。
执行: log | grep -n "Unable to handle"
结果: 行号 16133,故障地址 ffffffc054099984
后续执行: log | sed -n '16130,16200p' 获取完整 Oops
关键寄存器:
代码反汇编: 38401423 = LDRB W3, [X1], #1(LZ4 字面量拷贝循环中的第一个源读取)
假设更新: memcpy 在读取源地址的第一个字节时就触发了 fault。PTE 为零意味着物理页面不存在于页表中。需要确定该物理页面是从未分配还是已被释放。
4.4 检查故障页面状态
开放问题: 故障地址对应的物理页面是否存在?如果存在,其状态是什么?
推导: kmem <vaddr> 因 PTE 为零无法翻译。需要手动计算物理地址。
计算: 对于 Qualcomm arm64 平台:
PAGE_OFFSET =
ffffffc000000000(39-bit VA)PHYS_OFFSET =
0x80000000(Qualcomm 典型值)phys =
0xffffffc054099984 - 0xffffffc000000000 + 0x80000000=0xd4099984
执行: kmem -P d4099984
结果:
PAGE PHYSICAL MAPPING INDEX CNT FLAGS
ffffffff01302640 d4099000 dead000000000400 0 0 4000000000000000
关键发现:
MAPPING =
dead000000000400:这是内核的POISON_FREE模式!页面已被释放CNT (refcount) = 0:引用计数为零
FLAGS =
4000000000000000:仅 PG_head 位(复合页头)
后续检查: 对目标地址和相邻页面执行相同查询:
d4098000: dead000000000400 refcount=0 ← 前一页
d4099000: dead000000000400 refcount=0 ← 源页面(故障地址)
d40a0000: dead000000000400 refcount=0
d40a1000: dead000000000400 refcount=0 ← 目标页面
d40a2000: dead000000000400 refcount=0 ← 后一页
所有页面均已被释放! 从 d4098000 到 d40a2000 的 40KB+ 连续区域全部为 dead000000000400 毒化模式。
假设更新: 确认为 use-after-free。源页面和目标页面均已被 page allocator 释放。EROFS 解压缩路径访问了已释放的页面。
4.5 寻找释放路径
开放问题: 是什么释放了这些页面?是 inode eviction 还是 page reclaim?
推导: 需要找到 EROFS inode 并检查其状态,同时搜索 log 中的 EROFS 相关消息。
执行:
files 5432→ 获取任务打开的文件列表,发现大量/first_stage_ramdisk/路径的 EROFS 文件vm 5432→ 获取 VMA 列表,发现 EROFS 文件的 mmap 映射(如boot.oat、boot.vdex等)struct vm_area_struct.vm_file <vma>→0xffffff8068724240struct file.f_mapping <file>→0xffffff87802a30c0struct address_space.host <mapping>→ inode0xffffff87802a2e68struct inode.i_count,i_nlink,i_ino <inode>→ i_count=1, i_nlink=1, i_ino=32757962
Inode 状态: 健康(i_count=1, i_nlink=1)。排除了 inode eviction 假设。
关键 Log 发现:
[180317.592489] [2026:03:23 13:04:28](4)[85:kcompactd0]erofs: managed_cache_releasepage, nr_cached_pages = 3264 shrink[cnt = 51355 runs = 131694]
这是确凿证据! kcompactd0(PID 85, CPU 4)在 crash 前约 544ms 执行了 erofs: managed_cache_releasepage,累计回收了 51355 个页面,执行了 131694 次回收操作。
假设收敛: 根因确认为 EROFS managed cache 页面回收与 readahead 解压缩之间的竞争条件。
5. 直接证据
5.1 Oops 寄存器转储
Unable to handle kernel paging request at virtual address ffffffc054099984
Mem abort info:
ESR = 0x96000007
EC = 0x25: DABT (current EL), IL = 32 bits
SET = 0, FnV = 0
EA = 0, S1PTW = 0
Data abort info:
ISV = 0, ISS = 0x00000007
CM = 0, WnR = 0
swapper pgtable: 4k pages, 39-bit VAs, pgdp=00000000aab05000
[ffffffc054099984] pgd=0000000a12df6003, p4d=0000000a12df6003, pud=0000000a12df6003, pmd=00000000b12ed003, pte=0000000000000000
CPU: 7 PID: 5432 Comm: e.hwvoipservice Tainted: G W O 5.10.209-qki-consolidate-android12-9-gb43fede8be31-dirty #1
pc : __memcpy+0xac/0x180
lr : z_erofs_lz4_decompress_partial+0x404/0x5f0
sp : ffffffc0392230a0
x0 : ffffffc0540a1484 x1 : ffffffc054099984
x2 : 0000000000000001 x3 : 000000000000006e
x10: 5a5f000076454170
Code: 36080062 78402423 780024c3 36000542 (38401423)
5.2 故障页面物理状态
5.3 kcompactd0 EROFS 页面回收日志
[180317.592489] [2026:03:23 13:04:28](4)[85:kcompactd0]erofs: managed_cache_releasepage, nr_cached_pages = 3264 shrink[cnt = 51355 runs = 131694]
时间戳:crash 前约 544ms
CPU 4,PID 85(kcompactd0)
累计回收:51355 个页面,131694 次回收操作
5.4 Inode 状态
5.5 Bitflip 证据评估
故障地址
ffffffc054099984是有效的 kernel linear map 地址(~84MB 偏移)PTE 为完全零值(非损坏的 PTE),说明页面已被完全释放
x10 中的
5a5f000076454170是正常的 LZ4 压缩数据(ASCII "pAEv\0_Z")所有寄存器地址均在预期的 kernel linear map 范围内
相邻 5 个页面全部显示相同的
dead000000000400毒化模式结论:无 bitflip 证据
6. 推理与根因评估
6.1 根因机制
Use-After-Free:EROFS managed cache 页面回收与 readahead 解压缩竞争
EROFS 是 Android 系统中用于 first_stage_ramdisk 的只读压缩文件系统。当用户进程(e.hwvoipservice)通过 mmap 访问 EROFS 上的文件(如 boot.oat)时,内核的 page fault 处理路径会触发 readahead,进而调用 EROFS 的 z_erofs_vle_normalaccess_readpages 进行压缩页面的读取和 LZ4 解压缩。
EROFS 维护了一个 managed page cache 用于缓存压缩数据页面。当系统内存压力增大时,kcompactd0 通过 managed_cache_releasepage 回收这些缓存页面。
竞争时序:
T0: 用户进程访问 EROFS 文件的 mmap 区域,触发 page fault
T1: readahead 路径分配压缩源页面和解压缩目标页面
T2:
kcompactd0开始回收 EROFS managed cache 页面(包括正在使用的页面)T3: 源页面和目标页面被释放,PTE 被清除,页面内容被毒化为
dead000000000400T4:
z_erofs_lz4_decompress_partial调用__memcpy读取已释放的源页面T5: Translation fault → Oops → Kernel panic
6.2 竞争条件分析
竞争发生在两个路径之间:
路径 A(CPU 7, PID 5432):
el0_da→do_page_fault→filemap_fault→do_sync_mmap_readahead→page_cache_ra_unbounded→read_pages→z_erofs_vle_normalaccess_readpages→z_erofs_submit_and_unzip→z_erofs_vle_unzip→decompress_generic→lz4_decompress→z_erofs_lz4_decompress_partial→__memcpy(在此处 fault)路径 B(CPU 4, PID 85):
kcompactd0→ ... →erofs: managed_cache_releasepage(释放页面)
EROFS 的 managed_cache_releasepage 函数在释放页面时,可能没有充分检查页面是否正在被解压缩路径使用。这是一个 EROFS 子系统内部的页面生命周期管理缺陷。
6.3 排除的假设
7. 置信度、限制与分析天花板
置信度标签: high-probability
置信度说明:
直接证据(页面状态、log 消息、寄存器)完整且一致
竞争路径在时间和空间上都有明确的证据支持
页面毒化模式
dead000000000400是内核标准值,非异常
为什么不是 confirmed:
drgn 不可用,无法直接检查 EROFS 内部数据结构(如
z_erofs_pagevec、压缩 extent 描述符等)无法确定
managed_cache_releasepage是否缺少对 in-use 页面的引用检查无法确认是否为已知的 EROFS 上游 bug 还是 vendor 定制引入的问题
竞争的精确时序无法从 dump 中恢复
分析天花板:
无法确定
managed_cache_releasepage的具体代码路径(需要源码分析)无法确定是否存在其他并发的页面回收路径(如 direct reclaim)
无法确认 EROFS 的页面引用计数管理是否在所有路径上都正确
如果需要更高置信度的下一步:
获取 EROFS 源码,审查
managed_cache_releasepage的页面引用检查逻辑检查
z_erofs_vle_normalaccess_readpages中压缩页面的锁定和引用计数管理在 kernel 5.10.209 的 EROFS 代码中搜索已知的页面生命周期 bug
8. 建议措施与缓解方案
9. 关键命令附录
Report generated at: 2026-05-13 18:15
Session ID: 995b9abe-4788-4a5b-9b9b-f421340b0fba
Analyst: bsp-stability-ai by iliuqi
Author homepage: https://www.iliuqi.com