cmake可见性和AppArmor示例
This commit is contained in:
parent
0eeb4a7bb6
commit
4b4b933e3d
19 changed files with 661 additions and 0 deletions
207
AppArmor/demo1/README.md
Normal file
207
AppArmor/demo1/README.md
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
# AppArmor 入门 Demo(demo1)技术说明
|
||||
|
||||
> 环境:Ubuntu 24.04 x86_64(阿里云 ECS),内核 6.8.0-124-generic,
|
||||
> AppArmor parser 4.0.1,profile ABI 4.0。
|
||||
> 运行日期:2026-09-03,最终报告见同目录 `report.txt`。
|
||||
|
||||
## 1. Demo 目标
|
||||
|
||||
用一个最小可复现的例子演示 AppArmor(Linux 内核的强制访问控制 / MAC 模块)最核心的三个行为:
|
||||
|
||||
| # | 场景 | 预期 |
|
||||
|---|------|------|
|
||||
| 1 | 访问 profile 中**显式允许**的路径 | 放行 |
|
||||
| 2 | 访问 profile 中 `deny` 显式禁止的路径 | **静默拒绝**(EACCES,无审计日志) |
|
||||
| 3 | 访问 profile 中**完全没有提及**的路径 | 拒绝 + 内核记录 `DENIED` 审计日志 |
|
||||
|
||||
同时覆盖两种可执行体形态:
|
||||
|
||||
- **ELF 二进制**(`demo_app.c` 编译出的 `demo_app`)—— profile 直接 attach 到二进制路径;
|
||||
- **Shell 脚本**(`demo_shell.sh`)—— AppArmor 不认识"脚本",attach 的是**解释器**,转场(transition)发生在 exec `/bin/bash` 时。
|
||||
|
||||
## 2. 文件清单
|
||||
|
||||
```
|
||||
demo1/
|
||||
├── demo_app.c # C 测试程序:3 个文件访问探针
|
||||
├── demo_shell.sh # bash 测试脚本:3 个同类探针
|
||||
├── profile.elf # demo_app 的 AppArmor profile
|
||||
├── profile.script # demo_shell.sh 的 AppArmor profile
|
||||
├── run.sh # 一键运行脚本(需 root)
|
||||
└── report.txt # 运行产物:完整执行报告
|
||||
```
|
||||
|
||||
## 3. 测试程序设计
|
||||
|
||||
`demo_app.c` 与 `demo_shell.sh` 是同一组探针的两种实现:
|
||||
|
||||
```c
|
||||
/* 探针1: 白名单 —— profile 允许 rw,应当成功 */
|
||||
fopen("/tmp/demo_elf.log", "w");
|
||||
/* 探针2: 显式 deny —— deny /etc/shadow r,静默拒绝 */
|
||||
fopen("/etc/shadow", "r");
|
||||
/* 探针3: 未提及路径 —— 隐式默认拒绝,拒绝并记录 DENIED 日志 */
|
||||
fopen("/tmp/demo_forbidden.log", "w");
|
||||
```
|
||||
|
||||
三个探针分别命中 AppArmor 白名单规则的"允许"、`deny` 关键字、以及 enforce 模式下的默认拒绝(default deny)。
|
||||
|
||||
## 4. Profile 逐行解读
|
||||
|
||||
### 4.1 `profile.elf`(ELF 二进制)
|
||||
|
||||
```
|
||||
abi <abi/4.0>,
|
||||
|
||||
/home/fnzhang/Project/examples/AppArmor/demo1/demo_app flags=(enforce) {
|
||||
/home/fnzhang/Project/examples/AppArmor/demo1/demo_app r,
|
||||
/lib/** rm,
|
||||
/usr/lib/** rm,
|
||||
/usr/lib64/** rm,
|
||||
/etc/ld.so.cache r,
|
||||
/tmp/demo_elf.log rw,
|
||||
deny /etc/shadow r,
|
||||
}
|
||||
```
|
||||
|
||||
| 语法元素 | 含义 |
|
||||
|----------|------|
|
||||
| `abi <abi/4.0>,` | 声明 profile 所依据的规则 ABI 版本。不写会按 parser 默认(本机 4.0)并可能告警;显式写出可保证规则语义在不同 parser 版本间稳定 |
|
||||
| `路径 { ... }` | **匿名 attach 写法**:profile 的 attachment 点就是这段路径本身,进程 exec 该路径时自动套用此 profile,无需 `px` 转场规则 |
|
||||
| `flags=(enforce)` | 强制模式:违规即拒绝。对比 `flags=(complain)` 只记日志不拦截(调试用) |
|
||||
| `... r,` | `r` = read;文件规则末尾必须有逗号 |
|
||||
| `/lib/** rm` | `**` 跨目录通配;`rm` = read + mmap(动态库加载需要 m 权限) |
|
||||
| `/etc/ld.so.cache r` | 动态链接器要读的缓存 |
|
||||
| `/tmp/demo_elf.log rw` | 探针 1 的白名单 |
|
||||
| `deny /etc/shadow r` | 探针 2 的显式拒绝。`deny` 的语义是**无声拒绝且不产生审计日志**——与"没写这条规则时 enforce 模式的默认拒绝(有日志)"是两种不同行为 |
|
||||
|
||||
### 4.2 `profile.script`(Shell 脚本)
|
||||
|
||||
```
|
||||
abi <abi/4.0>,
|
||||
|
||||
/home/fnzhang/Project/examples/AppArmor/demo1/demo_shell.sh flags=(enforce) {
|
||||
/home/fnzhang/Project/examples/AppArmor/demo1/demo_shell.sh r,
|
||||
/bin/bash rm,
|
||||
/usr/bin/bash rm,
|
||||
...
|
||||
/usr/bin/cat rm,
|
||||
/tmp/demo_sh.log rw,
|
||||
deny /etc/shadow r,
|
||||
}
|
||||
```
|
||||
|
||||
关键点:
|
||||
|
||||
1. **attach 点是脚本路径,但真正受约束的是解释器进程**。内核 exec 脚本时会切换到 `/bin/bash` 执行;AppArmor 通过脚本头部的 `#!` 识别原始路径,把 profile 转场给 bash。所以:
|
||||
- 脚本自身要 `r`(bash 要读它);
|
||||
- `/bin/bash` 要 `rm`(exec 解释器 + mmap);
|
||||
- 脚本里用到的**每个外部命令**(这里是 `cat`)都要单独授权 —— 本 demo 故意**不**给 `cat` 授权 `/etc/shadow` 的读取,让 `cat` 本身可以 exec、但读文件时被拒,从而演示"子进程继承 profile 约束"。
|
||||
2. `@{HOME}` 这类**变量需要先 include tunables 才能用**(`#include <tunables/global>`)。本 profile 为保持自包含、便于教学阅读,直接写绝对路径。
|
||||
|
||||
## 5. 运行方式
|
||||
|
||||
```bash
|
||||
cd demo1
|
||||
chmod +x run.sh demo_shell.sh
|
||||
sudo sh run.sh # 必须以 root 运行:装载/卸载 profile 需要特权
|
||||
```
|
||||
|
||||
`run.sh` 流程:
|
||||
|
||||
```
|
||||
Pre-cleanup ─ apparmor_parser -R 卸载可能残留的旧 profile(幂等,容错)
|
||||
Step1 ─ gcc 编译 demo_app.c
|
||||
Step2 ─ apparmor_parser -Q 只做语法校验,不装载
|
||||
Step3 ─ cp 到 /etc/apparmor.d/ 并 apparmor_parser -r -W 装载(enforce)
|
||||
└ 之后 sleep 6 等 audit ratelimit 窗口恢复(见 §7)
|
||||
Step4/5 ─ 分别运行 ELF 与脚本,收集 3 个探针的输出
|
||||
Step6 ─ aa-status --json 过滤出两个 demo profile 的模式
|
||||
Step7 ─ dmesg 过滤 apparmor="DENIED" 的审计记录
|
||||
Step8 ─ 卸载 profile、删除 /etc/apparmor.d 下文件、清理 /tmp 探针文件
|
||||
```
|
||||
|
||||
## 6. 实测结果(report.txt 摘录)
|
||||
|
||||
**ELF:**
|
||||
|
||||
```
|
||||
[probe1] write /tmp/demo_elf.log: ALLOWED ← 白名单放行
|
||||
[probe2] open /etc/shadow: Permission denied ← deny 规则拒绝
|
||||
[probe3] open /tmp/demo_forbidden.log: Permission denied ← 默认拒绝
|
||||
-rw-r--r-- 1 root root 13 /tmp/demo_elf.log ← 允许的写确实落盘
|
||||
```
|
||||
|
||||
**脚本:**
|
||||
|
||||
```
|
||||
[probe1] write /tmp/demo_sh.log: ALLOWED
|
||||
demo_shell.sh: line 8: /usr/bin/cat: Permission denied ← cat 可 exec,读 shadow 被拒
|
||||
demo_shell.sh: line 12: /tmp/demo_forbidden.log: Permission denied
|
||||
```
|
||||
|
||||
**aa-status:**
|
||||
|
||||
```
|
||||
/home/fnzhang/Project/examples/AppArmor/demo1/demo_app: enforce
|
||||
/home/fnzhang/Project/examples/AppArmor/demo1/demo_shell.sh: enforce
|
||||
```
|
||||
|
||||
**内核审计日志(dmesg):**
|
||||
|
||||
```
|
||||
apparmor="DENIED" operation="mknod" profile="...demo_app"
|
||||
name="/tmp/demo_forbidden.log" requested_mask="c" denied_mask="c"
|
||||
apparmor="DENIED" operation="exec" profile="...demo_shell.sh"
|
||||
name="/usr/bin/cat" requested_mask="x" denied_mask="x"
|
||||
```
|
||||
|
||||
注意 DENIED 日志里**只有 probe3 和 `cat` 的事件,没有 `/etc/shadow`**——这正是 `deny` 规则(静默)与默认拒绝(记录日志)的区别在日志层面的直接证据。
|
||||
|
||||
## 7. 踩坑记录(本 demo 调通过程中实际遇到)
|
||||
|
||||
1. **profile 路径写死**:最初 profile 里是 `/home/fnzhang/aa_demo/...`(目录已不存在)。AppArmor 按可执行文件**绝对路径**匹配,路径变了 profile 就完全不生效。改 profile 前先确认二进制的实际路径。
|
||||
2. **`profile 名 flags=() 路径 {}` 是非法语法**:flags 必须放在 attachment 之后或用独立 `flags` 行;正确写法是 `路径 flags=(enforce) { ... }` 或 `profile 名 路径 flags=(enforce) { ... }`。非法语法会导致 `systemctl reload apparmor` 整体失败,且报错信息不在终端上、只在 `journalctl -u apparmor` 里。
|
||||
3. **`@{HOME}` 未声明报错**:`Found reference to variable HOME, but is never declared`——不带 `#include <tunables/global>` 时必须用绝对路径。
|
||||
4. **audit ratelimit 吞掉 DENIED 日志**:`systemctl reload apparmor` 会重载系统全部 200+ 个 profile,产生海量 `STATUS` 审计事件,触发内核 `printk_ratelimit`(`kauditd_printk_skb: xxx callbacks suppressed`),把随后几秒内 demo 真正的 DENIED 事件一并丢弃。解法:
|
||||
- 装载/卸载单条 profile 直接用 `apparmor_parser -r -W <file>` / `apparmor_parser -R <file>`,不要 `systemctl reload`;
|
||||
- 装载后 `sleep 6`(默认 ratelimit 窗口 5 秒)再触发拒绝。
|
||||
5. **`deny` 规则不产生日志是设计行为**:想在日志里看到拒绝事件,应使用"不写规则、靠 enforce 默认拒绝"的方式(demo 的 probe3 即为此设计)。
|
||||
6. **脚本 profile 授权对象**:给脚本写 profile 时,脚本内用到的外部命令都要逐个授权;漏掉会在运行时以 `Permission denied` 暴露。
|
||||
|
||||
## 8. 常用命令速查
|
||||
|
||||
```bash
|
||||
# 查看 AppArmor 是否启用
|
||||
cat /sys/module/apparmor/parameters/enabled # Y
|
||||
aa-status # 已装载 profile 列表与模式
|
||||
|
||||
# 装载 / 替换 / 卸载单条 profile(-W 同时写缓存)
|
||||
sudo apparmor_parser -r -W /etc/apparmor.d/xxx
|
||||
sudo apparmor_parser -R /etc/apparmor.d/xxx
|
||||
|
||||
# 仅语法校验
|
||||
apparmor_parser -Q /path/to/profile
|
||||
|
||||
# 查看某个进程当前的 profile
|
||||
cat /proc/<pid>/attr/current
|
||||
|
||||
# 查看拒绝日志
|
||||
sudo dmesg | grep 'apparmor="DENIED"'
|
||||
sudo journalctl -k | grep apparmor
|
||||
|
||||
# 临时把某 profile 切到 complain 模式排查问题
|
||||
sudo aa-complain /etc/apparmor.d/xxx
|
||||
sudo aa-enforce /etc/apparmor.d/xxx
|
||||
```
|
||||
|
||||
## 9. 与 SELinux 的对比(一句话版)
|
||||
|
||||
| | AppArmor | SELinux |
|
||||
|---|----------|---------|
|
||||
| 标签对象 | **路径**(文件路径 + 进程可执行路径) | inode 标签(label) |
|
||||
| 规则粒度 | 每应用一个 profile,白名单易读 | 策略全局统一,类型强制(TE) |
|
||||
| 学习成本 | 低,`aa-logprof` 可从日志生成规则 | 高 |
|
||||
| 适用发行版 | Ubuntu / Debian / SUSE | RHEL / Fedora / Android |
|
||||
|
||||
两者都是 LSM(Linux Security Module)钩子实现,内核只能启用其一(`CONFIG_LSM` 决定)。
|
||||
Loading…
Add table
Add a link
Reference in a new issue