LSM 初探
LSM(Linux Security Module)是 Linux 内核的安全模块框架。它允许开发者在内核中实现各种安全策略,如访问控制、权限管理等,而无需修改内核的核心代码。
编译支持 LSM 的内核
启用并重新编译:
scripts/config --enable SECURITY
scripts/config --enable SECURITYFS
make olddefconfig
make -j"$(nproc)"
然后重新启动新内核。
进入 BusyBox 后创建挂载点并挂载:
mkdir -p /sys/kernel/security
mount -t securityfs securityfs /sys/kernel/security
/sys/kernel/security 不是普通 sysfs 目录,它是 securityfs 的挂载点。
检查:
mount
ls -la /sys/kernel/security
cat /sys/kernel/security/lsm
/sys/kernel/security/lsm 应该列出当前实际启用的 LSM,例如:
capability,landlock,lockdown,yama,...
也可以确认内核是否认识 securityfs:
cat /proc/filesystems | grep securityfs
如果显示:
nodev securityfs
表示已经编译支持,只是之前没有挂载。如果没有任何输出,则通常是 CONFIG_SECURITYFS 没启用。
建议在 /init 中加入:
mkdir -p /sys/kernel/security
mount -t securityfs securityfs /sys/kernel/security
完整关系是:
/sys
└── kernel/
├── debug ← debugfs 挂载点
├── tracing ← tracefs 挂载点
└── security ← securityfs 挂载点
调试 LSM / SELinux
SELinux 是一个 LSM 模块,它在内核中注册了许多钩子函数,用于在关键操作时进行安全检查。
在 GDB 中设置:
break security_file_open
break selinux_file_open
continue
然后在虚拟机中执行:
cat /proc/version
命中 security_file_open() 后:
bt
可得
#0 selinux_file_open (file=0xffff888004460c00) at security/selinux/hooks.c:4259
#1 0xffffffff817e5a00 in security_file_open (file=0xffff888004460c00) at security/security.c:2739
#2 0xffffffff8157916a in do_dentry_open (f=f@entry=0xffff888004460c00, open=open@entry=0x0) at fs/open.c:924
#3 0xffffffff8157b56b in vfs_open (path=path@entry=0xffffc90000013d80, file=file@entry=0xffff888004460c00) at fs/open.c:1079
#4 0xffffffff815993ce in do_open (nd=0xffffc90000013d80, file=0xffff888004460c00, op=0xffffc90000013eb4) at fs/namei.c:4699
#5 path_openat (nd=nd@entry=0xffffc90000013d80, op=op@entry=0xffffc90000013eb4, flags=257) at fs/namei.c:4862
#6 0xffffffff8159a432 in do_file_open (dfd=dfd@entry=-100, pathname=pathname@entry=0xffff888003c72b40, op=op@entry=0xffffc90000013eb4)
at fs/namei.c:4891
#7 0xffffffff8157ba56 in do_sys_openat2 (dfd=-100, filename=<optimized out>, how=how@entry=0xffffc90000013ef8) at fs/open.c:1364
#8 0xffffffff8157bedc in do_sys_open (dfd=<optimized out>, filename=<optimized out>, flags=<optimized out>, mode=<optimized out>)
at fs/open.c:1370
#9 __do_sys_openat (dfd=<optimized out>, filename=<optimized out>, flags=<optimized out>, mode=<optimized out>) at fs/open.c:1386
#10 __se_sys_openat (dfd=<optimized out>, filename=<optimized out>, flags=<optimized out>, mode=<optimized out>) at fs/open.c:1381
#11 __x64_sys_openat (regs=<optimized out>) at fs/open.c:1381
#12 0xffffffff823ffb40 in do_syscall_x64 (regs=0xffffc90000013f58, nr=<optimized out>) at arch/x86/entry/syscall_64.c:63
#13 do_syscall_64 (regs=0xffffc90000013f58, nr=<optimized out>) at arch/x86/entry/syscall_64.c:94
#14 0xffffffff81000130 in entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:121
#15 0x0000000000000007 in ?? ()
#16 0x000000001e6a4710 in ?? ()
#17 0x0000000000000001 in ?? ()
#18 0x0000000000000006 in ?? ()
#19 0x00007ffe1cefcf10 in ?? ()
#20 0x000000001e6a5550 in ?? ()
p $lx_current()->pid
p $lx_current()->comm
p file->f_path.dentry->d_name.name
p/x file->f_flags
继续:
continue
应该命中:
selinux_file_open(struct file *file)
然后查看:
bt
p file->f_path.dentry->d_name.name
p file->f_mode
由于这个函数在符号表中的类型是小写 t:
ffffffff817f49a0 t selinux_file_open
表示它是局部文本符号,即源码中的 static 函数。GDB 仍然可以正常对它设置断点。
为什么 CONFIG_LSM 里有 Landlock/TOMOYO,却没启用
你的配置中有:
CONFIG_LSM="landlock,...,selinux,...,tomoyo,..."
但这只是:
如果这些 LSM 已经被编译进内核,它们应按什么顺序启用。
它不会自动设置:
CONFIG_SECURITY_LANDLOCK=y
CONFIG_SECURITY_TOMOYO=y
关系类似:
CONFIG_SECURITY_TOMOYO=y
决定是否编译 TOMOYO
CONFIG_LSM="...,tomoyo,..."
决定已编译的 TOMOYO 是否进入启用序列及其顺序
必须同时满足,LSM 才会实际运行。
如果想启用 TOMOYO 和 Landlock
执行:
scripts/config --enable SECURITY_TOMOYO
scripts/config --enable SECURITY_LANDLOCK
make olddefconfig
make -j"$(nproc)"
确认:
grep -E 'CONFIG_SECURITY_(TOMOYO|LANDLOCK)=' .config
nm vmlinux | grep -E 'tomoyo_file_open|hook_file_open'
然后务必用新生成的 bzImage 重新启动 QEMU,并让 GDB 加载同一次构建的 vmlinux。
启动后:
cat /sys/kernel/security/lsm
预计会包含:
capability,landlock,selinux,tomoyo
不过 TOMOYO 可能还有默认启用及策略加载相关配置;即使 hook 已注册,没有加载有效策略时,它通常不会拒绝普通操作。
确保 QEMU 与 GDB 使用同一份构建
这是内核调试非常重要的一点:
QEMU:arch/x86/boot/bzImage
GDB:vmlinux
二者必须来自同一次 make。
如果 QEMU 运行旧 bzImage,而 GDB 加载新 vmlinux,函数地址和源码行可能不匹配,表现为:
- 断点不命中
- 命中错误函数
- 调用栈异常
- 单步跳到奇怪的位置
重新构建和启动后,建议完全退出旧 GDB,再运行:
gdb ./vmlinux
一个更清晰的首次实验
暂时不必启用更多 LSM,先用现有 SELinux 观察:
break security_file_open
commands
silent
printf "\nsecurity_file_open: pid=%d comm=%s file=%s\n", \
$lx_current()->pid, $lx_current()->comm, \
file->f_path.dentry->d_name.name
bt 8
continue
end
如果希望停下来进入 SELinux:
break selinux_file_open
continue
但还有一点需要检查:SELinux “出现在 LSM 列表”不一定表示正在 enforcing。虚拟机中运行:
cat /sys/fs/selinux/enforce 2>/dev/null
cat /proc/cmdline
如果 selinuxfs 尚未挂载:
mkdir -p /sys/fs/selinux
mount -t selinuxfs selinuxfs /sys/fs/selinux
cat /sys/fs/selinux/enforce
结果:
1 → enforcing
0 → permissive
即使没有加载策略或者处于 permissive,selinux_file_open() 仍可能被调用;区别是它是否最终拒绝访问。
你现在最直接的断点组合是:
break security_file_open
break selinux_file_open
这已经足够观察完整的“VFS → LSM 框架 → 具体 LSM”分发过程。