feat(proc): Cleaner 跨进程修复(tool/input/output) + 共享内存安全模式 + plan §13

- Cleaner 协议统一为 cleaner.invoke(scope,name,text),覆盖工具/输入/输出三类
- 新增 ShmSecurityMode (safe/debug/full),Linux 审计探针,Windows crypto nonce
- plan.md 追加 §13 步骤分组
This commit is contained in:
JianFeeeee
2026-09-10 10:21:50 +08:00
parent 580d5f5501
commit 7a328679da
12 changed files with 645 additions and 45 deletions

View File

@ -0,0 +1,62 @@
//go:build linux
package proc
import (
"fmt"
"os"
"strings"
)
// countOurShmMappings 在 Linux 上审计本 Host 的共享段被多少进程映射。
//
// 原理memfd 是匿名对象,文件系统无名,但在每个映射它的进程的
// /proc/<pid>/maps 里会有一行形如:
//
// 7f.. 7f.. rw-s 00000000 00:00 7312 .../memfd:hastagectx (deleted)
//
// 我们遍历 Supervisor.List() 给出的全部子进程 PID统计它们的 maps 中
// 出现 "memfd:hastagectx" 的行数。
// homed 自身的映射不算(它本来就该有一份)。
//
// 局限:/proc/<pid>/maps 需要同 UID/同命名空间读。读不到的 PID 直接跳过,
// 返回 ok=true 但少计——审计是告警不是判据,宁缺毋滥。
func (h *Host) countOurShmMappings() (int, bool) {
if h.sup == nil {
return 0, false
}
procs := h.sup.List()
if len(procs) == 0 {
return 0, true
}
// memfd 的"文件名"在 shmalloc_linux.go 里是 "hastagectx"。
const memfdTag = "memfd:hastagectx"
total := 0
for _, p := range procs {
if !p.Alive {
continue
}
if n, err := countMemfdLinesForPID(p.PID, memfdTag); err == nil {
total += n
}
}
return total, true
}
// countMemfdLinesForPID 读 /proc/<pid>/maps统计含 tag 的行数。
//
// 一个进程对同一 memfd 多次 mmap 会产生多行;我们计数"行数"而非
// "是否出现",这样能发现一个插件进程泄漏了多余映射的情况。
func countMemfdLinesForPID(pid int, tag string) (int, error) {
data, err := os.ReadFile(procMapsPath(pid))
if err != nil {
return 0, fmt.Errorf("读 /proc/%d/maps: %w", pid, err)
}
return strings.Count(string(data), tag), nil
}
// procMapsPath 组装 /proc/<pid>/maps 路径。pid 是内核自己 spawn 的进程号,
// 无用户输入注入面。
func procMapsPath(pid int) string {
return fmt.Sprintf("/proc/%d/maps", pid)
}