package main import ( "strings" "testing" "golang.org/x/sys/unix" ) /* 边界工具的判据分两层: - **行为**(真跑内核边界):`deploy/check-sandbox.sh` —— 界内可写/界外被拒/ 软链接逃逸/子进程继承/对照,19 项。 - **算法**(不碰内核):这里。它们决定"套什么样的边界",错了会以很难看的方式 失败(比如把内核不认识的位塞进 handled_access_fs ⇒ create_ruleset EINVAL ⇒ **整条边界都套不上**,而错误信息只有 invalid argument)。 */ // 按 ABI 逐位裁剪是硬要求:内核不认识的位会让 create_ruleset 直接 EINVAL。 func TestHandleForTrimsByABI(t *testing.T) { base := uint64(fsWrite | fsRemove | fsMake) abi1 := handleFor(1) if abi1 != base { t.Fatalf("ABI 1 只该有写/删/建这些位,实际 0x%x", abi1) } if abi1&fsRefer != 0 || abi1&fsTruncate != 0 { t.Fatal("ABI 1 不该带上 REFER/TRUNCATE —— 内核不认识那两位") } abi2 := handleFor(2) if abi2 != base|fsRefer { t.Fatalf("ABI 2 = 基础位 + REFER,实际 0x%x", abi2) } if abi2&fsTruncate != 0 { t.Fatal("ABI 2 不该带上 TRUNCATE(那是 ABI 3 的)") } abi3 := handleFor(3) if abi3 != base|fsRefer|fsTruncate { t.Fatalf("ABI 3 该带上 TRUNCATE,实际 0x%x", abi3) } // 单调:高 ABI 的集合必须是低 ABI 的超集(否则"升级内核反而更松")。 if abi1&^abi2 != 0 || abi2&^abi3 != 0 { t.Fatal("ABI 集合必须单调递增") } } // 文件目标只能用文件类权限:带上目录类权限会让 add_rule 返回 EINVAL // (实测放行 `cmd 2>/dev/null` 给 /dev/null 加规则时撞上的)。 func TestFileHandleExcludesDirectoryRights(t *testing.T) { for abi := 1; abi <= 4; abi++ { h := fileHandleFor(abi) if h&fsWrite == 0 { t.Fatalf("ABI %d:文件目标必须允许 WRITE_FILE(否则这个开关毫无用处)", abi) } for name, bit := range map[string]uint64{ "MAKE_DIR": unix.LANDLOCK_ACCESS_FS_MAKE_DIR, "MAKE_REG": unix.LANDLOCK_ACCESS_FS_MAKE_REG, "REMOVE_FILE": unix.LANDLOCK_ACCESS_FS_REMOVE_FILE, "REMOVE_DIR": unix.LANDLOCK_ACCESS_FS_REMOVE_DIR, "REFER": fsRefer, } { if h&bit != 0 { t.Fatalf("ABI %d:文件目标的权限里不该有 %s(add_rule 会 EINVAL)", abi, name) } } // 规则里的 allowed 必须是 handled 的子集,否则内核对不上。 if h&^handleFor(abi) != 0 { t.Fatalf("ABI %d:文件权限不是 handled 的子集", abi) } } } func TestParseArgs(t *testing.T) { o, err := parseArgs([]string{"--rw", "/a", "--rw=/b", "--rw-file", "/dev/null", "--", "bash", "-c", "x"}) if err != nil { t.Fatal(err) } if strings.Join(o.rwDirs, ",") != "/a,/b" { t.Fatalf("rw 目录解析错了:%v", o.rwDirs) } if strings.Join(o.rwFiles, ",") != "/dev/null" { t.Fatalf("rw 文件解析错了:%v", o.rwFiles) } if strings.Join(o.cmd, " ") != "bash -c x" { t.Fatalf("命令解析错了:%v", o.cmd) } // 缺 `--` 必须报错:否则命令会被当成未知开关,而"什么都没有"看起来像"没配 rw"。 if _, err := parseArgs([]string{"--rw", "/a", "bash"}); err == nil { t.Fatal("缺 `--` 应当报错") } if _, err := parseArgs([]string{"--rw"}); err == nil { t.Fatal("--rw 后面没值应当报错") } if _, err := parseArgs([]string{"--bogus", "--", "true"}); err == nil { t.Fatal("未知开关应当报错") } }