vm.mmap_rnd_bits 引发 ASAN CPU 100% 问题解析

Root cause 摘要:Linux 6.8(Ubuntu24.04 默认)vm.mmap_rnd_bits=32 和容器内 gcc‑12.2 libasan 不兼容。ASAN 在 main() 执行前初始化阶段随机触发段错误,ASAN 的信号处理器死循环打印 AddressSanitizer:DEADLYSIGNAL,把 CPU 打满 100%。最小复现程序:int main(){return 0;} 开启 ASAN 编译,约 1/3 概率复现卡死,栈全部落在 libasan/ldso(AsanInitInternal → SizeClassAllocator64::Init),业务代码完全还没执行,排除业务、流、信号业务逻辑问题。

1. 为什么 vm.mmap_rnd_bits=32 会造成这个现象

vm.mmap_rnd_bits 控制 mmap 虚拟地址的随机化比特位数,数值越大,地址空间ASLR随机偏移范围越大。

  • 内核6.8+ Ubuntu24.04 默认:vm.mmap_rnd_bits = 32,mmap 会给出跨度极大的64位虚拟地址偏移
  • ASAN 的 SizeClassAllocator64 内存分配器初始化阶段,会预留一大块固定布局的虚拟地址区间用于影子内存(shadow memory)。
    libasan‑12.2 的地址布局假设ASLR随机偏移不会过大 ;当内核给出极端高位的随机mmap地址,会和ASAN预留的影子内存地址范围发生虚拟地址冲突/重叠
  1. AsanInitInternal 初始化64位分配器,调用 mmap 预留影子内存区域
  2. 内核因 mmap_rnd_bits=32 返回一个冲突的随机虚拟地址
  3. 内部触发 SIGSEGV(在 main 之前,业务代码尚未运行)
  4. ASAN注册的SIGSEGV信号处理器接管;但此时ASAN自身初始化还没完成,信号处理内部状态不完整
  5. 信号 handler 尝试打印 DEADLYSIGNAL,又触发访问错误,信号处理递归触发信号 → 死循环,CPU 100%。

关键点:不是普通崩溃退出,是信号handler无限循环,进程不退出,CPU跑满。最小空main程序就复现,证明和应用代码无关。

2. 现象特征(用于排查确认)

  1. 进程CPU持续100%,不崩溃、不core dump、不退出

  2. stderr 持续刷屏:AddressSanitizer:DEADLYSIGNAL

  3. gdb bt 全部在libasan内部:

    #0 ... inside signal handler
    #1 __libasan::AsanInitInternal
    #2 __libasan::SizeClassAllocator64::Init
    #3 ld‑linux.so startup, before main()

  4. 复现是间歇性:约1/3概率,ASLR随机,不是每次必现。

  5. 仅触发条件组合:

    • Host内核 ≥6.8,vm.mmap_rnd_bits=32(Ubuntu24.04默认)
    • 容器内部 gcc‑12.2 libasan

    即使容器是旧的Ubuntu/Debian,只要宿主机内核是6.8+,mmap的ASLR行为由宿主机内核决定!容器不能隔离 vm.mmap_rnd_bits,这是关键坑。

⚠️ 重要提醒:vm.mmap_rnd_bits宿主机内核sysctl,不是容器内可独立设置的参数。容器无法覆盖宿主机该内核行为,哪怕容器内部sysctl读出来别的值,实际mmap行为跟随host kernel。

3. 验证复现(最小case)

c 复制代码
// main.c
int main(){return 0;}

编译:

bash 复制代码
gcc -fsanitize=address main.c -o asan_demo
# 循环跑,大概1/3次卡死CPU100%
for i in {1..100}; do ./asan_demo; done

gdb attach 卡死进程,可以看到栈在asan初始化+信号处理循环,还没有进入main。

4. 修复方案(三种可选)

方案A:宿主机降低 vm.mmap_rnd_bits(根治,推荐测试环境)

把宿主机的该内核参数下调,例如设为 28,规避过大ASLR偏移触发asan地址冲突。

bash 复制代码
# 临时生效 host
sysctl -w vm.mmap_rnd_bits=28

# 永久写入 /etc/sysctl.conf
echo "vm.mmap_rnd_bits = 28" >> /etc/sysctl.conf
sysctl -p

注意:必须改宿主机,容器内部sysctl修改该参数会报错无权限,不生效。

方案B:ASAN侧环境变量规避,不改动内核

ASAN 提供环境变量收紧虚拟地址随机,规避内核过大ASLR偏移:

bash 复制代码
export ASAN_OPTIONS=mmap_randomize_shadow_memory=0
./your_asan_binary

关闭asan影子内存自身的mmap随机化,可以规避该冲突。适合CI容器场景,不需要修改宿主机sysctl。

方案C:升级libasan版本

gcc‑13+ / llvm‑16+ 的 libasan 修复了对高 mmap_rnd_bits 的兼容性,改进64位分配器地址布局逻辑,不再受32bit rnd bits影响。

容器内升级gcc到13+,替换libasan。

方案D:临时规避,关闭ASLR(不推荐生产,仅调试)

bash 复制代码
setarch $(uname -m) -R ./asan_demo

5. 为什么这不是业务代码bug

  1. 故障发生在main()执行之前,AsanInitInternal,应用代码一行都没跑;
  2. 空main最小程序稳定复现;
  3. 触发条件完全依赖host内核vm.mmap_rnd_bits=32和特定版本libasan组合;
  4. 故障根因:libasan‑12.2 的 SizeClassAllocator64::Init 没有处理内核返回极大ASLR随机偏移,初始化阶段SIGSEGV后信号处理器递归死循环。

6. 补充背景与相关内核/LLVM信息

Linux 6.8 调高了默认 vm.mmap_rnd_bits,提升安全熵;但是老版本ASAN 64位分配器对超大mmap随机偏移缺少健壮处理:

  • ASAN 64bit模式需要预留大片连续虚拟地址空间用于shadow memory;
    当内核返回的mmap随机偏移过高,会抢占asan需要的预留地址区间,初始化mmap调用失败,触发segfault;
    初始化未完成时,asan信号handler没有做好保护,发生fault‑in‑signal‑handler,无限循环,CPU打满。

很多CI用Ubuntu24.04 host跑老版本gcc‑12容器,就会踩这个间歇性卡死bug,现象非常迷惑:测试随机挂、CPU100、看不到业务栈。

相关推荐
Fnetlink11 小时前
Fnet 云网安 260807
服务器·网络·人工智能·安全·网络安全
XR1234567881 小时前
学校图书馆网络改造,方案谁更优?
网络·数据库·php
进击切图仔1 小时前
在 windows 和 ubuntu 中配置 host 文件
linux·windows·ubuntu
郭涤生2 小时前
C++ 零拷贝(Zero-Copy)
linux·开发语言·c++
weixin_445476682 小时前
LIMS 3.0 测试环境部署过程记录
linux·状态模式
AI 小老六2 小时前
Agent Runtime 如何用 Session、Memory、User Profile 和 Skill 实现外部学习
服务器·人工智能·学习·ai·架构·自动化
The Chosen One9852 小时前
【操作系统】操作系统引导、虚拟机、进程通信、信号
linux·运维·服务器·操作系统·虚拟机·信号
宵时待雨2 小时前
linux笔记归纳14:Socket编程TCP
linux·服务器·网络·笔记·tcp/ip
小张同学a.3 小时前
zabbix企业级监控平台3——服务监控与API实战
linux·运维·mysql·nginx·tomcat·zabbix