# srp-kernel-linux **Repository Path**: aurawing/srp-kernel-linux ## Basic Information - **Project Name**: srp-kernel-linux - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-16 - **Last Updated**: 2026-05-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SRP Guard — Linux 内核模块 (`srp_guard.ko`) 抗勒索 / 数据防泄漏方案中的“瘦内核代理”。模块本身只做 **拦截 → 组装 → 上报 → 缓存判决**,所有策略判定都交给用户态守护进程(`arv-daemon`)。 - 内核版本:3.10 ~ 6.18+(覆盖 CentOS 7 / RHEL 8/9 / Ubuntu 18.04~24.04 / 麒麟 V10/V11 / UOS V20/V25 / Deepin V23 等) - 体系结构:x86_64 与 arm64 - 拦截机制:kprobe + LSM `security_*` / `security_path_*` 钩子(无需打补丁、无需重建内核) - 协议:`include/srp/srp_uapi.h` —— 与 Windows 版本完全二进制兼容 --- ## 1. 目录结构 ``` srp-kernel-linux/ ├── Kbuild # 内核 Makefile 片段,列出对象文件 ├── Makefile # 顶层 Makefile,自动适配 eventfd_signal 签名 ├── include/srp/ │ ├── srp_uapi.h # 跨平台 UAPI(操作枚举、消息结构) │ ├── srp_ring.h # 共享环形缓冲区头与索引规则 │ └── srp_dev.h # /dev/srp_guard 的 IOCTL 定义 ├── src/ │ ├── srp_main.c # 模块 init/exit、参数声明 │ ├── srp_core.c # 环形缓冲区、misc 设备、RPC、poller 线程 │ ├── srp_hooks.c # 所有 kprobe 拦截点 + 路径解析 │ ├── srp_proc_cache.c # 进程信息缓存 + verdict 缓存 │ ├── srp_path_filter.c # 受保护目录前缀过滤 │ ├── srp_internal.h # 私有声明 │ └── srp_compat.h # 跨内核版本兼容垫片 └── test/ ├── srp_smoke_test.c # 用户态烟雾测试:连环读取 + 主动触发所有 hook ├── centos7_test.sh # CentOS 7 一键编译/加载/测试脚本 └── ubuntu_test.sh # Ubuntu/Debian 一键脚本 ``` --- ## 2. 整体架构 ``` ┌────────────── userspace ──────────────┐ │ arv-daemon (Go / C) │ │ ├─ 读 req 环 (mmap) │ │ ├─ 规则引擎判定 verdict │ │ ├─ ioctl SET_VERDICT 写回缓存 │ │ └─ ioctl SET_PATH_FILTER 等控制面 │ └──┬──────────────────────▲──────────────┘ │ mmap / eventfd │ ioctl ─ ─ ─ ─ ─ ─ ─ ─ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ▼ │ ┌──────────── kernel ────────┴────────────────────────┐ │ /dev/srp_guard (misc device) │ │ │ │ │ ▼ │ │ Shared Ring (vmalloc_user) │ │ [ srp_ring_hdr_t | req slots[N] | resp slots[N] ] │ │ │ │ ┌─ kprobe pre_handlers (atomic) ──────────────┐ │ │ │ security_file_open / security_path_unlink │ │ │ │ security_path_rename / security_path_mkdir │ │ │ │ security_path_rmdir / security_inode_setattr │ │ │ │ wake_up_new_task / do_exit / begin_new_exec │ │ │ └────┬──────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ 路径解析 → 路径白名单 → proc cache → verdict cache │ │ │ │ │ ▼ miss │ │ srp_ring_push_async → eventfd_signal(req_evt) │ │ │ │ │ ▼ │ │ 本次操作放行(fail-open;下次依赖 verdict cache) │ └──────────────────────────────────────────────────────┘ ``` ### 关键设计点 1. **共享内存 + eventfd 双工通道**。`vmalloc_user` 分配的环形缓冲区通过 `mmap` 同时映射到内核与用户态;通知用 `eventfd(2)` 双向,避免 `read()` / `ioctl()` 的拷贝开销。 2. **请求/响应分离的环**。`req_head/req_tail` 由内核生产、用户态消费;`resp_head/resp_tail` 反向。 3. **以"(进程, 路径前缀槽)"维度做异步判决**(协议 v2)。kprobe 处于 atomic 上下文,**不能阻塞**,所以: - cache 命中(ALLOW/DENY 且未过期)→ 立即生效; - cache 未命中 → push 一条 notify 后**立即放行本次**,daemon 处理完调用 `SRP_IOCTL_SET_VERDICT(pid, prefix_id, verdict, ttl)` 写回对应槽位,**下一次**同一 (PID, 路径前缀) 组合的 I/O 即可在内核命中。 - 协议 v1 的 verdict cache 仅按 PID 索引,会出现"PID X 在规则 A 上拿到 ALLOW 后访问规则 B 也直接放行"的跨规则泄漏;v2 把每个 PID 的 cache 拆成 `SRP_MAX_VERDICT_SLOTS`(=64)个槽位,槽位号即内核侧 path filter 命中的下标,从根本上隔离不同规则。 4. **路径前缀白名单**。`srp_path_filter.c` 维护最多 64 条受保护前缀,匹配时返回 `prefix_id`(0..63 或 -1)。该 id 同时充当 verdict cache 的槽位号,因此"路径过滤"和"裁决缓存"自然对齐。`SET_PATH_FILTER` 写入会使所有 PID 的 64 个槽位全部失效,避免规则重排后槽号错位。 5. **跨内核兼容**: - `eventfd_signal` 单/双参数签名,由 `Makefile` 编译期探测; - `security_inode_setattr` 在 Ubuntu 20.04 HWE/5.12+/6.3+ 上签名不同,运行时根据"哪个寄存器看起来像 dentry"自动选; - `security_path_*` 不存在(CONFIG_SECURITY_PATH=n)时自动回落到 `security_inode_*`; - `begin_new_exec` (≥5.9) / `search_binary_handler` 二选一; - 通过 `current->fs->pwd/root` + 自维护的 `sb→vfsmount` 缓存解决 ostree、UOS V25 的“分裂挂载”路径解析问题。 --- ## 3. 拦截操作清单 | `srp_op_t` | 触发的 kprobe (优先 → 兜底) | 上报访问位 | 是否同步拒绝¹ | |--------------------|-----------------------------------------|---------------------------|---------------| | `SRP_OP_FILE_OPEN` | `security_file_open` | `READ` | ✓(cache 命中时) | | `SRP_OP_DELETE` | `security_path_unlink` → `security_inode_unlink` | `DELETE` | ✓ | | `SRP_OP_RENAME` | `security_path_rename` → `security_inode_rename` | `RENAME` | ✓ | | `SRP_OP_MKDIR` | `security_path_mkdir` → `security_inode_mkdir` | `WRITE` | ✓ | | `SRP_OP_RMDIR` | `security_path_rmdir` → `security_inode_rmdir` | `DELETE` | ✓ | | `SRP_OP_SET_INFO` | `security_inode_setattr` | `SET_ATTR` | ✓ | | `SRP_OP_PROC_CREATE` | `wake_up_new_task` | — | 仅通知 | | `SRP_OP_PROC_EXIT` | `do_exit` | — | 仅通知 | | (内部 cache 失效) | `begin_new_exec` / `search_binary_handler` | — | exec 后清进程 verdict | > ¹ "同步拒绝" 指内核侧能直接返回 `-EACCES` 阻断本次系统调用。kprobe 路径上**只有 verdict cache 命中 DENY 时**才会真正阻断;首次访问统一 fail-open。 > 不拦截 `read/write/mmap` 等热路径。如有更严格的需求,可在策略侧让 daemon 通过 `SET_VERDICT` 把进程标黑,下次 OPEN 即被同步阻断。 --- ## 4. 编译 ### 4.1 依赖 - `kernel-devel` / `linux-headers-$(uname -r)`(必须与运行内核版本一致) - `gcc`、`make` - 内核需启用 `CONFIG_KPROBES=y`(绝大多数发行版默认开启) ### 4.2 一键命令 ```bash cd srp-kernel-linux make # 编译,产出 ./srp_guard.ko make clean # 清理 sudo make install # 安装到 /lib/modules/$(uname -r)/extra/ ``` `make` 时若想指定其他内核源码: ```bash make KDIR=/usr/src/linux-headers-6.6.0 ``` 构建时会输出探测结果之一: ``` srp_guard: detected single-arg eventfd_signal srp_guard: detected two-arg eventfd_signal ``` 如果探测错误(例如自定义内核里 `eventfd_signal` 签名特殊),可手工覆盖: ```bash make EXTRA_CFLAGS="-DSRP_EVENTFD_SIGNAL_SINGLE_ARG" ``` --- ## 5. 加载与卸载 ```bash # 默认参数(enforce 模式) sudo insmod srp_guard.ko # 推荐:先用 log-only 模式跑通 sudo insmod srp_guard.ko srp_default_mode=1 srp_fail_open=1 # 卸载(先确保 daemon 已断开 /dev/srp_guard) sudo rmmod srp_guard ``` 加载后会出现: - 字符设备 `/dev/srp_guard`(misc,权限 0660) - `dmesg` 输出 `srp_guard: loaded successfully (/dev/srp_guard)`,并列出每个成功注册的 kprobe ### 5.1 模块参数 | 参数 | 默认 | 说明 | |------|------|------| | `srp_slot_size` | 4096 | 单个 slot 字节数(512 ~ 65536) | | `srp_slot_count` | 1024 | slot 数量(必须 2 的幂,16 ~ 16384) | | `srp_default_mode` | 0 | 0=enforce, 1=log-only, 2=disabled | | `srp_fail_open` | 1 | 出错/超时时 1=放行,0=拒绝 | | `srp_rpc_timeout_ms` | 5000 | 同步 RPC 超时(目前文件 hook 不走该路径) | | `srp_scratch_pool_size` | 0 | 0 表示自动 = `cpu*16`(最少 256,最多 4096) | | `srp_debug` | 0 | 调试打印开关 | | `disable_hooks`(仅诊断) | 0 | 位掩码屏蔽特定 hook,详见 `srp_hooks.c` 顶部注释 | 运行时可读写: ```bash echo 1 | sudo tee /sys/module/srp_guard/parameters/srp_default_mode ``` --- ## 6. 用户态对接(控制面) 字符设备 `/dev/srp_guard` 暴露的接口(`include/srp/srp_dev.h`): | IOCTL | 方向 | 说明 | |-------|------|------| | `SRP_IOCTL_GET_RING_DESC` | R | 取 `slot_size / slot_count / ring_size` | | `SRP_IOCTL_SET_EVENTFD` | W | 一次性传入 `int fds[2] = {req_fd, resp_fd}`,**触发 `g_conn.ready=true` 并启动 poller** | | `SRP_IOCTL_SET_MODE` | W | 0=enforce, 1=log-only, 2=disabled | | `SRP_IOCTL_SET_OWNER_PID` | W | 覆盖 daemon 自身 PID(默认在 `SET_EVENTFD` 时取 `current->tgid`) | | `SRP_IOCTL_SET_PATH_FILTER` | W | 全量下发受保护目录前缀(最多 64 个,每个 ≤1024B),同时清空所有 PID 的全部 64 个 verdict cache 槽位 | | `SRP_IOCTL_GET_STATS` | R | 取 `srp_stats_t` | | `SRP_IOCTL_SET_VERDICT` | W | `{pid, verdict, cache_ttl_ms, prefix_id}` 写入 (pid, prefix_id) 槽位(v2,prefix_id<0 时丢弃) | | `SRP_IOCTL_DETACH` | — | 断开 daemon,`Ready=false`,所有 hook 退化为放行 | 接入伪代码: ```c fd = open("/dev/srp_guard", O_RDWR); ioctl(fd, SRP_IOCTL_GET_RING_DESC, &desc); ring = mmap(NULL, desc.ring_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); int fds[2] = { eventfd(0, EFD_NONBLOCK), eventfd(0, EFD_NONBLOCK) }; ioctl(fd, SRP_IOCTL_SET_EVENTFD, fds); /* req_fd = fds[0], resp_fd = fds[1] */ ioctl(fd, SRP_IOCTL_SET_PATH_FILTER, &paths); /* 受保护目录 */ ioctl(fd, SRP_IOCTL_SET_MODE, &mode); while (running) { poll({fds[0], POLLIN}, 1, -1); /* 从 ring->req_offset 起的 slot 数组读消息(含 srp_req_hdr_t.prefix_id),按 op 分派 */ /* 命中策略后 ioctl SET_VERDICT 写回 verdict cache —— 务必带上请求里的 prefix_id, * 否则内核无法把裁决落到正确的 (pid, prefix) 槽位 */ } ``` > **协议 v2 备注**:`SRP_PROTOCOL_VER` 现为 `2`。`srp_req_hdr_t` / `srp_resp_hdr_t` 末尾各新增 `int32_t prefix_id` + `uint32_t _pad`,`srp_ioctl_set_verdict_t` 把原 `_pad` 字段重命名为 `prefix_id`(结构体总大小不变)。`prefix_id` 取自内核 path filter 命中的下标(0..63 或 -1),daemon 必须把请求里的值原样回填到响应(或 SET_VERDICT ioctl)。 --- ## 7. 测试 ### 7.1 一键脚本 仓库提供两个发行版的一键脚本,覆盖:依赖安装 → 编译 → 加载 → 触发 → 校验事件 → 卸载。 ```bash # CentOS 7 / RHEL 7 / Anolis 7 / Kylin V10 (centos7-base) sudo bash test/centos7_test.sh # Ubuntu / Debian / Deepin / UOS / 麒麟 V11 (apt 系) sudo bash test/ubuntu_test.sh ``` ### 7.2 手工冒烟测试 ```bash sudo insmod srp_guard.ko srp_default_mode=1 gcc -std=gnu99 -o test/srp_smoke_test test/srp_smoke_test.c -I include sudo test/srp_smoke_test # 默认运行,主动触发并校验所有 hook sudo test/srp_smoke_test -v # 详细打印每条消息 sudo test/srp_smoke_test -c # 顺便回写 SET_VERDICT,验证 cache 路径 ``` 预期最后一段输出形如: ``` FILE_OPEN : 7 OK SET_INFO : 3 OK DELETE : 1 OK RENAME : 1 OK MKDIR : 1 OK RMDIR : 1 OK PROC_CREATE : 1 OK PROC_EXIT : 1 OK === Smoke Test PASSED === ``` ### 7.3 观察 kprobe 注册情况 ```bash mount -t debugfs none /sys/kernel/debug 2>/dev/null cat /sys/kernel/debug/kprobes/list | grep -E 'security_|wake_up|do_exit|exec' ``` --- ## 8. 常见问题 | 现象 | 排查 | |------|------| | `insmod` 报 `Operation not permitted` | 内核启用了模块签名(Secure Boot)。临时方案:关闭 SB 或导入自签名密钥;正式方案:用厂商证书 sign-file 模块。 | | `dmesg` 出现 `failed to register kprobe 'security_path_*'` | 当前内核 `CONFIG_SECURITY_PATH=n`。模块会自动回落到 `security_inode_*`,功能等价但拿不到 vfsmount,路径分辨率会走启发式回退。 | | 卸载时 `rmmod` 卡住 | daemon 还持有 `/dev/srp_guard` 的 fd。先停 daemon 再 `rmmod`。代码上 `srp_conn_free_ring` 会同步 RCU 等待 hook 退出。 | | 路径解析输出相对路径(开头不是 `/`) | 当前 dentry 既不属于 `pwd.mnt`,也不属于 `root.mnt`,且 sb→vfsmount 缓存还没被该挂载点上的某次 OPEN 填充过。让 daemon 触发一次该目录的文件 OPEN 后再观察。 | | Kylin V11 / 6.6 内核出现递归 kprobe 死锁 | 已通过 per-CPU `srp_hook_active` 递归保护规避;如新版本仍报 `ftrace` 相关 lockup,请用 `disable_hooks=` 参数二分定位。 | | 性能问题 | 1) 确认 `srp_path_matches_filter` 命中范围足够窄;2) 把 `srp_default_mode` 临时设为 `disabled` 观察基线;3) `cat /sys/module/srp_guard/parameters/...` 与 `SRP_IOCTL_GET_STATS` 比对。 | --- ## 9. 许可证 GPL-2.0(与 Linux 内核兼容)。详见 `LICENSE`。