123 lines
4.6 KiB
Bash
Executable file
123 lines
4.6 KiB
Bash
Executable file
#!/bin/bash
|
||
# 运行方法:
|
||
# step1: chmod +x run.sh demo_shell.sh
|
||
# step2: sudo sh run.sh
|
||
set -e
|
||
|
||
WORK_DIR=$(cd "$(dirname "$0")" && pwd)
|
||
cd "${WORK_DIR}"
|
||
|
||
if [ "$(id -u)" -ne 0 ];then
|
||
echo "please run with sudo: sudo sh run.sh"
|
||
exit 1
|
||
fi
|
||
|
||
ELF_BIN="${WORK_DIR}/demo_app"
|
||
SCRIPT_FILE="${WORK_DIR}/demo_shell.sh"
|
||
|
||
PROFILE_ELF_NAME="home.fnzhang.aa_demo.demo_app"
|
||
PROFILE_SCRIPT_NAME="home.fnzhang.aa_demo.demo_shell"
|
||
PROFILE_ELF="/etc/apparmor.d/${PROFILE_ELF_NAME}"
|
||
PROFILE_SCRIPT="/etc/apparmor.d/${PROFILE_SCRIPT_NAME}"
|
||
|
||
REPORT="${WORK_DIR}/report.txt"
|
||
> "${REPORT}"
|
||
|
||
log(){
|
||
echo "$1"
|
||
echo "$1" >> "${REPORT}"
|
||
}
|
||
|
||
echo "========================================"
|
||
echo "AppArmor Demo start, workdir: ${WORK_DIR}"
|
||
echo "========================================"
|
||
|
||
# ========== 【前置清理:解决上次残留冲突】 ==========
|
||
log ""
|
||
log "===== Pre‑cleanup: remove leftover profile ====="
|
||
# 卸载内核中可能残留的旧 profile(-R 按 profile 名卸载)
|
||
apparmor_parser -R "${PROFILE_ELF}" 2>/dev/null || true
|
||
apparmor_parser -R "${PROFILE_SCRIPT}" 2>/dev/null || true
|
||
# 清掉 /etc/apparmor.d 下旧文件(含上次失败残留的错误 profile)
|
||
rm -f "${PROFILE_ELF}" "${PROFILE_SCRIPT}"
|
||
# 注意:不使用 systemctl reload apparmor —— 它会重载全部 200+ 个 profile,
|
||
# 触发内核 printk ratelimit(kauditd callbacks suppressed),
|
||
# 把随后 demo 真正的 DENIED 审计事件一并丢弃。
|
||
log "pre‑cleanup done"
|
||
|
||
# ========== Step1 compile C elf ==========
|
||
log ""
|
||
log "===== Step1 compile C elf ====="
|
||
gcc "${WORK_DIR}/demo_app.c" -o "${ELF_BIN}"
|
||
chmod +x "${ELF_BIN}"
|
||
log "compile done: ${ELF_BIN}"
|
||
|
||
# ========== Step2 validate profile syntax ==========
|
||
log ""
|
||
log "===== Step2 validate profile syntax ====="
|
||
apparmor_parser -Q "${WORK_DIR}/profile.elf" && log "profile.elf syntax OK"
|
||
apparmor_parser -Q "${WORK_DIR}/profile.script" && log "profile.script syntax OK"
|
||
|
||
# ========== Step3 install profile ==========
|
||
log ""
|
||
log "===== Step3 install profile ====="
|
||
cp "${WORK_DIR}/profile.elf" "${PROFILE_ELF}"
|
||
cp "${WORK_DIR}/profile.script" "${PROFILE_SCRIPT}"
|
||
apparmor_parser -r -W "${PROFILE_ELF}" || { rm -f "${PROFILE_ELF}"; log "ERROR: elf profile load failed"; exit 1; }
|
||
apparmor_parser -r -W "${PROFILE_SCRIPT}" || { rm -f "${PROFILE_SCRIPT}"; log "ERROR: script profile load failed"; exit 1; }
|
||
|
||
log "profile installed & enforce enabled"
|
||
|
||
# 等待内核 audit ratelimit 窗口恢复,避免 DENIED 事件被 printk ratelimit 吞掉
|
||
sleep 6
|
||
|
||
# ========== Step4 run elf demo_app ==========
|
||
log ""
|
||
log "===== Step4 run elf demo_app ====="
|
||
rm -f /tmp/demo_elf.log /tmp/demo_forbidden.log
|
||
"${ELF_BIN}" 2>&1 | tee -a "${REPORT}" || log "(demo_app returned non-zero / was killed - expected)"
|
||
log "-- verify /tmp/demo_elf.log exists (allowed write):"
|
||
ls -l /tmp/demo_elf.log 2>&1 | tee -a "${REPORT}"
|
||
|
||
# ========== Step5 run shell script ==========
|
||
log ""
|
||
log "===== Step5 run shell script ====="
|
||
rm -f /tmp/demo_sh.log /tmp/demo_forbidden.log
|
||
"${SCRIPT_FILE}" 2>&1 | tee -a "${REPORT}" || log "(demo_shell.sh returned non-zero / was killed - expected)"
|
||
log "-- verify /tmp/demo_sh.log exists (allowed write):"
|
||
ls -l /tmp/demo_sh.log 2>&1 | tee -a "${REPORT}"
|
||
|
||
# ========== Step6 aa-status snapshot ==========
|
||
log ""
|
||
log "===== Step6 aa-status snapshot ====="
|
||
aa-status --json 2>/dev/null | python3 -c "
|
||
import json,sys
|
||
d = json.load(sys.stdin)
|
||
for name, mode in sorted(d.get('profiles', {}).items()):
|
||
if 'demo_app' in name or 'demo_shell' in name:
|
||
print(f' {name}: {mode}')
|
||
" | tee -a "${REPORT}"
|
||
|
||
# ========== Step7 check deny evidences ==========
|
||
log ""
|
||
log "===== Step7 AppArmor DENIED logs ====="
|
||
# 等 ratelimit 窗口过去再收集(sleep 6 已在 Step3 后做过,这里直接查)
|
||
dmesg 2>/dev/null | grep -iE 'apparmor=\"DENIED\"' | grep -iE 'demo_app|demo_shell' | tail -10 | tee -a "${REPORT}" \
|
||
|| journalctl -k -b --no-pager 2>/dev/null | grep -iE 'apparmor=\"DENIED\"' | grep -iE 'demo_app|demo_shell' | tail -10 | tee -a "${REPORT}"
|
||
if [ -z "$(dmesg 2>/dev/null | grep 'apparmor=\"DENIED\"' | grep -iE 'demo_app|demo_shell')" ]; then
|
||
log "(提示: 若此处为空, 可能被内核 printk ratelimit 抑制; 显式 deny 规则本身不产生日志)"
|
||
fi
|
||
|
||
# ========== Step8 clean resources ==========
|
||
log ""
|
||
log "===== Step8 clean resources ====="
|
||
apparmor_parser -R "${PROFILE_ELF}" 2>/dev/null || true
|
||
apparmor_parser -R "${PROFILE_SCRIPT}" 2>/dev/null || true
|
||
rm -f "${PROFILE_ELF}" "${PROFILE_SCRIPT}"
|
||
|
||
rm -f "${ELF_BIN}"
|
||
rm -f /tmp/demo_elf.log /tmp/demo_sh.log /tmp/demo_forbidden.log
|
||
|
||
log ""
|
||
log "========================================"
|
||
log "Demo finished, report file: ${REPORT}"
|