问题背景
在项目bringup时发现,将手机的selinux权限设为 permissive 手机会死机
bringup期间,手机进入kernel,但是还无法进入android界面
问题分析
拿到fulldump的第一时间,我们可以立即查看内核日志,查看一下是否有很直观的报错日志,于是我们可以看到如下的日志:
[exception_reboot_reason]: kernel_crash
[exception_panic_reason]: UBSAN: array index out of bounds: Fatal exception
[exception_time]: 1970-01-01_08-00-29
[exception_file_info]: not-bugon
[exception_task_id]: 926
[exception_task_family]: [mkswap, 926][system_perf_ini, 922]
[exception_pc_symbol]: [<ffffffd00f2b27a4>] zram_submit_bio+0xee4/0xf28 [uzram]
[exception_stack_info]: [<ffffffd00dfe3260>] prepare_exception_info+0x290/0x5f8 [sysdump]
[<ffffffd00dfe6904>] sysdump_panic_event+0x420/0x5b0 [sysdump]
[<ffffffd010084e1c>] notifier_call_chain+0x8c/0x170
[<ffffffd0100852ac>] atomic_notifier_call_chain+0x44/0x68
[<ffffffd00ff55ce4>] vpanic+0x180/0x3b4
[<ffffffd00ff55b60>] panic+0x50/0x54
[<ffffffd00ff54ce8>] die+0xe0/0xe8
[<ffffffd00ff54ef4>] ubsan_brk_handler+0x34/0x4c
[<ffffffd00ff72500>] call_el1_break_hook+0x6c/0xb8
[<ffffffd00ff7262c>] do_el1_brk64+0x2c/0x58
[<ffffffd011316f10>] el1_brk64+0x34/0x54
[<ffffffd011316b10>] el1h_64_sync_handler+0x6c/0xcc
[<ffffffd00ff51340>] el1h_64_sync+0x84/0x88
[<ffffffd00f2b27a4>] zram_submit_bio+0xee4/0xf28 [uzram]
[<ffffffd01074a1b4>] __submit_bio+0x124/0x23c
[<ffffffd0107483b4>] submit_bio_noacct_nocheck+0x114/0x2dc
[<ffffffd0107488d8>] submit_bio_noacct+0x35c/0x54c
[<ffffffd010748cb8>] submit_bio+0x1f0/0x210
[<ffffffd01048a98c>] submit_bh_wbc+0x150/0x1bc
[<ffffffd01048a5d8>] __block_write_full_folio+0x3c8/0x62c
[<ffffffd01048c954>] block_write_full_folio+0x190/0x1b0
[<ffffffd01073dc70>] blkdev_writepages+0x84/0xe4
[<ffffffd01031e1f4>] do_writepages+0xd8/0x1a4
[<ffffffd01030d100>] file_write_and_wait_range+0x1c8/0x1fc
[<ffffffd01073e3b0>] blkdev_fsync+0x48/0x74
[<ffffffd010479b68>] vfs_fsync_range+0xc0/0x154
[<ffffffd010479c6c>] __arm64_sys_fsync+0x3c/0x6c
[<ffffffd00ff94ab8>] invoke_syscall+0x60/0xec
[<ffffffd00ff949f8>] el0_svc_common+0xb0/0xe0
[<ffffffd00ff9493c>] do_el0_svc+0x24/0x30
[<ffffffd011317200>] el0_svc+0x40/0xb0
[<ffffffd01131714c>] el0t_64_sync_handler+0x68/0xdc 于是我们可以得到这是一个数组越界的问题,那这个就好解决了,只需要定位到代码行,然后查看一下它是如何造成越界的就基本可以得到根因。所以由于比较简单,下面的分析我都交给了AI,通过自行开发的 kernel-panic-killer 进行探索
AI 分析的过程
**步骤 3: 反汇编 `zram_submit_bio+0xee4`**
- **假设**: 需要找到触发 UBSAN 的具体指令
- **结果**: `brk #0x5512` — UBSAN 的 array-index-out-of-bounds 陷阱指令
- **决策**: 需要回溯找到实际的数组访问代码
**步骤 4: 反汇编 `zram_submit_bio+0xec0`**
- **假设**: 找到 UBSAN 错误报告的参数设置代码
- **结果**: 发现代码模式:
- `adrp/add` 加载格式字符串
- `mov w1, w27` — 越界索引参数
- `mov w2, w25` — 数组边界参数
- `brk #0x800` — UBSAN 处理器陷阱
- `bl panic` — 调用 panic
- **决策**: 需要找到 w27 和 w25 的来源
**步骤 5: 反汇编 `zram_submit_bio+0xc00`**
- **假设**: 找到数组索引和边界的计算代码
- **结果**: 发现关键代码路径:
- `zram_read_from_zspool` 调用后的错误检查
- `tbnz w27, #31` — 检查返回值是否为负(错误)
- **决策**: 需要进一步回溯找到压缩算法选择代码
**步骤 6: 反汇编 `zram_submit_bio+0x200`**
- **假设**: 找到压缩算法索引的计算和验证代码
- **结果**: 发现核心代码模式:
```
+548: ldr x8, [x20] ; x8 = zram->table
+552: adrp x10, _MODULE_DATA_START_uzram
+556: ldr w10, [x10, #3592] ; w10 = current_algo
+564: sub w27, w10, #0x1 ; w27 = current_algo - 1
+572: cmp w27, #0x7 ; 检查 current_algo - 1 > 7
+588: b.cs zram_submit_bio+3812 ; 如果越界,跳转到 UBSAN/panic
+596: ldr x0, [x8, w27, uxtw #3] ; 加载 compress_streams[current_algo-1]
```
- **决策**: 关键发现!需要验证 `current_algo` 的值
**步骤 7: 内存读取 `0xffffffd00f2b7e08`**
- **假设**: 读取 `current_algo` 变量的值
- **结果**: `0x00000000` — **current_algo 为 0!**
- **决策**: 确认根因!current_algo = 0 导致 current_algo - 1 = 0xFFFFFFFF(无符号下溢)
**步骤 8: 符号验证**
- `sym 0xffffffd00f2b7e08` → `current_algo [uzram]` ✅
- `sym 0xffffffd00f2b7e10` → `huge_class_size [uzram]` = 3625(已正常初始化)✅
- `sym 0xffffffd00f2b7e20` → `log_level [uzram]` = 2(正常)✅AI很快速的就恢复异常现象,并反汇编calltrace,将出现越界的变量找到了,就是这个 current_algo
源码分析
将源码提供给工具后,AI的分析如下
通过 `/data/share-data/admin/src/zram_drv.c` 源码验证:
1. **`current_algo` 声明**(第 77 行): `static u32 current_algo;` — 默认值为 0
2. **写路径**(第 2018-2021 行): `zram->comps[algo - 1]` — 当 algo=0 时,索引为 0xFFFFFFFF
3. **读路径**(第 1813-1816 行): `zram->comps[prio - 1]` — 同样的越界风险
4. **设置路径**(第 1344-1350 行): `current_algo = prios + 1` — 仅通过 sysfs 写入触发
5. **模块初始化**(第 3152-3154 行): 设置 `comp_algs` 但不设置 `current_algo`AI分析的证据链
Panic 签名
[37.925284] Internal error: UBSAN: array index out of bounds: 00000000f2005512 [#1] SMP
[38.262128] Kernel panic - not syncing: UBSAN: array index out of bounds: Fatal exception故障上下文
CPU: 4 UID: 0 PID: 926 Comm: mkswap Tainted: G W OE 6.18.16 #1 PREEMPT
pc : zram_submit_bio+0xee4/0xf28 [uzram]
lr : zram_submit_bio+0x6c/0xf28 [uzram]
sp : ffffffc08536b780current_algo 内存状态
sym 0xffffffd00f2b7e08 → current_algo [uzram]
rd -x 0xffffffd00f2b7e08 → 0x0000000000000000 (值为 0)相邻变量状态
sym 0xffffffd00f2b7e10 → huge_class_size [uzram] = 3625 (已初始化)
sym 0xffffffd00f2b7e20 → log_level [uzram] = 2 (正常)内核日志时间线
[24.174343] modprobe: Loading module /vendor/lib/modules/uzram.ko
[24.195410] zram: Added device: zram0
[24.196556] zram: zram to flash mode 2
[24.200958] initcall: init_module [uzram] time=8.696269ms
[37.879205] zram0: detected capacity change from 0 to 12582912
[37.925235] type=1400 audit(37.875:110): avc: denied { read write } for comm="mkswap" ... permissive=1
[37.925284] Internal error: UBSAN: array index out of bounds反汇编证据
zram_submit_bio+556: ldr w10, [x10, #3592] ; w10 = current_algo = 0
zram_submit_bio+564: sub w27, w10, #0x1 ; w27 = 0 - 1 = 0xFFFFFFFF
zram_submit_bio+572: cmp w27, #0x7 ; 0xFFFFFFFF > 7 → carry set
zram_submit_bio+588: b.cs zram_submit_bio+3812 ; 分支跳转 → BRK #0x5512 → panic源码证据
// zram_drv.c 第 77 行
static u32 current_algo; // 默认值为 0
// zram_drv.c 第 2018-2021 行(写路径)
algo = current_algo; // algo = 0
zram_set_comp(zram, index, algo);
zstrm = zcomp_stream_get(zram->comps[algo - 1]); // comps[0xFFFFFFFF] → 越界!
// zram_drv.c 第 1344-1349 行(设置路径)
for (prios = 0; prios < backends_count; prios++) {
if (!strcmp(backends[prios]->name, compressor)) {
current_algo = prios + 1; // 仅通过 sysfs 写入设置
break;
}
}推理与根因评估
崩溃机制
当 CONFIG_UNISOC_ZRAM_DYNAMIC_COMP 启用时,uzram 模块使用 current_algo 全局变量跟踪当前选择的压缩算法。该变量是 1-based 索引(1 = 第一个算法,2 = 第二个,依此类推),默认值为 0 表示"未选择"。
写路径 zram_write_page 使用 current_algo - 1 作为 zram->comps[] 数组的索引。当 current_algo = 0 时:
current_algo - 1 = 0 - 1 = 0xFFFFFFFF(u32 无符号下溢)zram->comps[0xFFFFFFFF]→ 数组越界访问UBSAN 检测到越界,触发
brk #0x5512陷阱内核 panic
根因评估
直接原因: current_algo 在 mkswap 提交写 I/O 时为 0。
根本原因: current_algo 只能通过用户空间写入 /sys/block/zram0/comp_algorithm sysfs 节点来设置。在正常启动流程中,init 脚本应按以下顺序执行:
echo lzo > /sys/block/zram0/comp_algorithm— 设置压缩算法echo <size> > /sys/block/zram0/disksize— 设置磁盘大小mkswap /dev/block/zram0— 格式化 swap
在此案例中,步骤 1 从未执行(日志中无 comp_algorithm 记录),但步骤 2 和 3 执行了,导致 panic。
SELinux 关联: SELinux 策略可能阻止了 comp_algorithm 的设置(如果 system_perf_init 域没有 sysfs_zram 写权限),或者 init 脚本本身存在顺序错误。在 enforcing 模式下,mkswap 的 I/O 操作会被阻止,不会触发 panic;在 permissive 模式下,操作被允许,暴露了这个 bug。
越界访问证明状态
结论: 越界访问已确认。current_algo = 0 是直接证据,0xFFFFFFFF 索引是算术推导,源码验证了完整的因果链。
为何selinux打开就出现了呢?

selinux被设置为 permissive后,服务 system_perf_init 回去执行 mkswap 对 zram0 操作,而zram0因为背景还处于bringup期间,系统还无法启动

zram0的 com_algorithm 算法还没有被设置,导致内核的uzram驱动中的 current_algo 还是0,这样就出现了溢出
但是说到底内核的代码还是存在缺陷的!因为我们要记住一点,无论任何情况内核都应该避免出现死机的情况!
代码修复
在 zram_write_page 和 read_compressed_page 中添加 current_algo 有效性检查:
// zram_drv.c 第 2018 行附近
#if IS_ENABLED(CONFIG_UNISOC_ZRAM_DYNAMIC_COMP)
algo = current_algo;
if (unlikely(algo == 0)) {
pr_err_ratelimited("uzram: current_algo not configured, rejecting write\n");
return -EINVAL;
}
zram_set_comp(zram, index, algo);
zstrm = zcomp_stream_get(zram->comps[algo - 1]);