examples/AppArmor/demo1/README.md

207 lines
9.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# AppArmor 入门 Demodemo1技术说明
> 环境Ubuntu 24.04 x86_64阿里云 ECS内核 6.8.0-124-generic
> AppArmor parser 4.0.1profile ABI 4.0。
> 运行日期2026-09-03最终报告见同目录 `report.txt`。
## 1. Demo 目标
用一个最小可复现的例子演示 AppArmorLinux 内核的强制访问控制 / 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 |
两者都是 LSMLinux Security Module钩子实现内核只能启用其一`CONFIG_LSM` 决定)。