//go:build linux package instanceexec import ( "errors" "os" "path/filepath" "strings" "syscall" "testing" "github.com/carlosframework/platform/internal/instancefs" "github.com/carlosframework/platform/internal/provision" ) // This is an opt-in kernel witness, not a mock or a go-test-on-the-host check. // The external sandbox wrapper must build this package's test binary and run // only this test through main's isolated tenant launcher as the tenant payload. // It must first verify that the edge's delegated cgroup controls exist and are // writable by carlos in the host namespace, and that all four journal paths // below are existing regular files in that namespace. Use disposable sandbox // fixtures, never create fixtures in a production journal directory. Set // CARLOS_CLAIM_ISOLATION_HOST to the sandbox host, then set both switches below // only after those host-side checks pass. The assertion cannot establish the // existence of files deliberately hidden from its own namespace. // // A missing opt-in skips. Missing host fixture attestation or incorrect mount // facts fail. Local CI skips do not prove isolation and cannot enable rollout. // Opens have no O_CREATE, O_TRUNC or write calls, even if isolation is broken. func TestClaimTenantCannotWriteContainmentOrJournals(t *testing.T) { if os.Getenv("CARLOS_CLAIM_ISOLATION_KERNEL") != "1" { t.Skip("requires sandbox fixtures and main's isolated tenant launcher") } if os.Getenv("CARLOS_CLAIM_ISOLATION_HOST_FIXTURES_VERIFIED") != "1" { t.Fatal("external wrapper must verify host-side journal and delegated cgroup fixtures") } host := os.Getenv("CARLOS_CLAIM_ISOLATION_HOST") if !provision.ValidHostShape(host) { t.Fatal("sandbox host fixture is missing or invalid") } if os.Geteuid() == 0 || os.Getpid() != 1 { t.Fatal("witness must run as the non-root tenant payload in its private PID namespace") } mounts, err := os.ReadFile("/proc/self/mountinfo") if err != nil { t.Fatal(err) } for mountpoint, filesystem := range map[string]string{"/data": "tmpfs", "/sys/fs/cgroup": "cgroup2"} { readOnly, err := instancefs.MountOptionOf(mountpoint, "ro") if err != nil || !readOnly { t.Fatalf("%s must be a read-only mount: %v, %v", mountpoint, readOnly, err) } found := false for _, line := range strings.Split(string(mounts), "\n") { left, right, ok := strings.Cut(line, " - ") fields, fs := strings.Fields(left), strings.Fields(right) if ok && len(fields) >= 6 && fields[4] == mountpoint && len(fs) >= 1 { found = fs[0] == filesystem } } if !found { t.Fatalf("%s must have filesystem %s in the tenant namespace", mountpoint, filesystem) } } // The tenant's /data is a fresh read-only tmpfs exposing only instances. // This is the hiding mechanism; ENOENT alone would be a vacuous assertion. entries, err := os.ReadDir("/data") if err != nil || len(entries) != 1 || entries[0].Name() != "instances" || !entries[0].IsDir() { t.Fatalf("unexpected tenant data namespace: %+v, %v", entries, err) } for _, name := range []string{"cgroup.procs", "cgroup.threads", "cgroup.subtree_control"} { path := filepath.Join("/sys/fs/cgroup/carlos.slice/carlos-edge.service", name) info, err := os.Stat(path) if err != nil || !info.Mode().IsRegular() { t.Fatalf("delegated control fixture %s must exist: %v", path, err) } stat, ok := info.Sys().(*syscall.Stat_t) if !ok || int(stat.Uid) != os.Geteuid() || info.Mode().Perm()&0200 == 0 { t.Fatalf("%s must be delegated to the tenant UID before read-only mount enforcement", path) } f, err := os.OpenFile(path, os.O_WRONLY, 0) if err == nil { f.Close() t.Fatalf("tenant can open %s for write", path) } if !errors.Is(err, syscall.EROFS) { t.Fatalf("%s refusal must be EROFS, not missing fixture or DAC: %v", path, err) } } // Opening cgroup.procs and cgroup.threads for write is necessary to move // a process or thread. Their refusal proves migration is unavailable // without attempting to move this process or alter another cgroup. for _, path := range []string{ "/data/.hostclaims/launches.db", "/data/.hostclaims/inventory.db", "/data/.hostguard/claim-isolation-fixture.json", filepath.Join("/data/hostguard", host+".json"), } { f, err := os.OpenFile(path, os.O_WRONLY, 0) if err == nil { f.Close() t.Fatalf("tenant can open journal %s for write", path) } if !errors.Is(err, syscall.ENOENT) { t.Fatalf("journal %s must be hidden by the verified tenant data mount: %v", path, err) } } }