WIP claim Plan 14 track E checkpoint 27 (not for merge)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 files changed,
+256
−2
internal/host/plan14_generation_census_kernel_linux_test.go+23 −0internal/host/plan14_generation_namespace_root_linux_test.go+226 −0internal/host/rootgeneration_kernel_linux.go+5 −0internal/provision/testdata/plan14-root-reads-e.tsv+2 −2
CI passed — run details
CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok amadan.net/carlos/platform.git/launchctx 0.011s
? amadan.net/carlos/platform.git/launchctx/controlname [no test files]
? amadan.net/carlos/platform.git/launchctx/wireid [no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m
ok github.com/carlosframework/platform 350.433s
? github.com/carlosframework/platform/cmd/changelog [no test files]
ok github.com/carlosframework/platform/cmd/console 3.172s
ok github.com/carlosframework/platform/cmd/moveprobe 93.274s
ok github.com/carlosframework/platform/cmd/zddprobe 0.708s
ok github.com/carlosframework/platform/internal/activator 233.847s
ok github.com/carlosframework/platform/internal/agentsock 0.016s
ok github.com/carlosframework/platform/internal/alerting 0.003s
ok github.com/carlosframework/platform/internal/analytics 0.019s
ok github.com/carlosframework/platform/internal/appconfig 2.345s
ok github.com/carlosframework/platform/internal/apperr 0.070s
ok github.com/carlosframework/platform/internal/applog 0.136s
ok github.com/carlosframework/platform/internal/awsinv 0.007s
ok github.com/carlosframework/platform/internal/boxchoice 0.004s
ok github.com/carlosframework/platform/internal/boxid 0.645s
? github.com/carlosframework/platform/internal/boxid/boxidtest [no test files]
? github.com/carlosframework/platform/internal/boxid/internal/signtest [no test files]
ok github.com/carlosframework/platform/internal/boxvolumes 0.012s
ok github.com/carlosframework/platform/internal/caps 0.011s
ok github.com/carlosframework/platform/internal/certwarm 0.002s
ok github.com/carlosframework/platform/internal/cgroup 0.696s
ok github.com/carlosframework/platform/internal/changelog 0.308s
ok github.com/carlosframework/platform/internal/clamd 0.055s
? github.com/carlosframework/platform/internal/cliflag [no test files]
ok github.com/carlosframework/platform/internal/console 364.720s
? github.com/carlosframework/platform/internal/controlname [no test files]
? github.com/carlosframework/platform/internal/controlsock [no test files]
ok github.com/carlosframework/platform/internal/dbdoor 6.246s
ok github.com/carlosframework/platform/internal/dbdoor/dbdoortest 0.017s
ok github.com/carlosframework/platform/internal/dbverify 0.014s
ok github.com/carlosframework/platform/internal/dnsrec 0.008s
ok github.com/carlosframework/platform/internal/docsite 0.011s
ok github.com/carlosframework/platform/internal/domains 2.123s
ok github.com/carlosframework/platform/internal/doorkey 0.009s
ok github.com/carlosframework/platform/internal/economics 0.012s
ok github.com/carlosframework/platform/internal/edge 26.080s
? github.com/carlosframework/platform/internal/edgeproof [no test files]
ok github.com/carlosframework/platform/internal/edgesfile 0.016s
ok github.com/carlosframework/platform/internal/fleetapi 0.126s
ok github.com/carlosframework/platform/internal/gate 1.665s
ok github.com/carlosframework/platform/internal/genselect 0.019s
ok github.com/carlosframework/platform/internal/geo 0.020s
ok github.com/carlosframework/platform/internal/geoip 0.031s
ok github.com/carlosframework/platform/internal/historyreadback 0.155s
ok github.com/carlosframework/platform/internal/host 133.256s
ok github.com/carlosframework/platform/internal/hostactors 0.383s
ok github.com/carlosframework/platform/internal/hostbox 2.315s
ok github.com/carlosframework/platform/internal/hostclaim 0.501s
ok github.com/carlosframework/platform/internal/hostevidence 0.015s
ok github.com/carlosframework/platform/internal/hostexec 4.919s
ok github.com/carlosframework/platform/internal/hostfence 0.029s
ok github.com/carlosframework/platform/internal/hostfiles 10.501s
ok github.com/carlosframework/platform/internal/hostfloor 0.014s
ok github.com/carlosframework/platform/internal/hostguard 1.010s
ok github.com/carlosframework/platform/internal/hostops 1.456s
? github.com/carlosframework/platform/internal/hostops/fencerelease [no test files]
ok github.com/carlosframework/platform/internal/hostops/internal/fencerelease 0.016s
ok github.com/carlosframework/platform/internal/hostquarantine 0.016s
ok github.com/carlosframework/platform/internal/hostrelocate 0.448s
ok github.com/carlosframework/platform/internal/hostresolve 0.143s
ok github.com/carlosframework/platform/internal/hostwrite 0.015s
ok github.com/carlosframework/platform/internal/infraview 0.014s
ok github.com/carlosframework/platform/internal/instancectl 1.830s
ok github.com/carlosframework/platform/internal/instanceexec 2.718s
ok github.com/carlosframework/platform/internal/instancefs 0.021s
ok github.com/carlosframework/platform/internal/instancekey 0.002s
ok github.com/carlosframework/platform/internal/instanceupdates 0.025s
ok github.com/carlosframework/platform/internal/layout 1.454s
ok github.com/carlosframework/platform/internal/lease 9.453s
ok github.com/carlosframework/platform/internal/lease/leasetest 0.020s
ok github.com/carlosframework/platform/internal/ledger 0.012s
ok github.com/carlosframework/platform/internal/mail 0.164s
ok github.com/carlosframework/platform/internal/maintenance 0.082s
ok github.com/carlosframework/platform/internal/maintenanceexec 5.549s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.077s
ok github.com/carlosframework/platform/internal/metrics 0.005s
ok github.com/carlosframework/platform/internal/moves 21.020s
ok github.com/carlosframework/platform/internal/moves/archive 0.757s
ok github.com/carlosframework/platform/internal/objstore 0.185s
ok github.com/carlosframework/platform/internal/ops 0.166s
ok github.com/carlosframework/platform/internal/park 0.210s
ok github.com/carlosframework/platform/internal/privatefile 0.006s
ok github.com/carlosframework/platform/internal/provision 13.776s
ok github.com/carlosframework/platform/internal/publicrelease 0.010s
? github.com/carlosframework/platform/internal/regionname [no test files]
ok github.com/carlosframework/platform/internal/registry 4.009s
ok github.com/carlosframework/platform/internal/relay 7.233s
ok github.com/carlosframework/platform/internal/release 21.437s
ok github.com/carlosframework/platform/internal/rollout 3.827s
ok github.com/carlosframework/platform/internal/rolloutwords 0.003s
ok github.com/carlosframework/platform/internal/rootaws 0.007s
ok github.com/carlosframework/platform/internal/rootparticipation 0.007s
ok github.com/carlosframework/platform/internal/rootstate 0.025s
ok github.com/carlosframework/platform/internal/s3compat 0.005s
ok github.com/carlosframework/platform/internal/safefile 0.004s
ok github.com/carlosframework/platform/internal/schedule 2.308s
ok github.com/carlosframework/platform/internal/sim/hostlife 9.815s
ok github.com/carlosframework/platform/internal/sim/litestreamfacts 3.919s
ok github.com/carlosframework/platform/internal/status 0.038s
ok github.com/carlosframework/platform/internal/steering 0.006s
ok github.com/carlosframework/platform/internal/stopevidence 0.199s
ok github.com/carlosframework/platform/internal/svcrole 0.002s
ok github.com/carlosframework/platform/internal/telemetry 0.004s
ok github.com/carlosframework/platform/internal/termfence 0.004s
ok github.com/carlosframework/platform/internal/tunnelclient 0.003s
ok github.com/carlosframework/platform/internal/unixdial 0.002s
ok github.com/carlosframework/platform/internal/updatewords 0.004s
ok github.com/carlosframework/platform/internal/usage 0.004s
ok github.com/carlosframework/platform/internal/version 0.002s
ok github.com/carlosframework/platform/internal/vet 1.574s
=== cross ===
--- shard 0 of 5 ---
cross-compiling darwin/arm64
--- shard 1 of 5 ---
cross-compiling darwin/amd64
--- shard 2 of 5 ---
cross-compiling linux/arm64
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
minio: cached, digest verified
mc: cached, digest verified
minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
buckets and scoped user ready
=== RUN TestDeliveredContractMinio
=== RUN TestDeliveredContractMinio/put
=== RUN TestDeliveredContractMinio/get_round-trips_the_body
=== RUN TestDeliveredContractMinio/list_shows_the_key
=== RUN TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN TestDeliveredContractMinio/delete_removes_it
=== RUN TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
--- PASS: TestDeliveredContractMinio/put (0.00s)
--- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
--- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
--- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
--- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok github.com/carlosframework/platform/internal/objstore 0.024s
=== RUN TestT4StorageProbeMinio
2026/10/14 17:28:07 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DE757E4FC940E3, HostID: 7c420053e0fbcd7f41653fdd92ffa2802c903d05303b1aa15adc2e8bb65d183f, api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
--- PASS: TestT4StorageProbeMinio (0.01s)
PASS
ok github.com/carlosframework/platform/internal/host 0.024s
minio contract: PASS
diff --git a/internal/host/plan14_generation_census_kernel_linux_test.go b/internal/host/plan14_generation_census_kernel_linux_test.go| index 43444945a..9a03f8a9f 100644 |
| --- a/internal/host/plan14_generation_census_kernel_linux_test.go |
| +++ b/internal/host/plan14_generation_census_kernel_linux_test.go |
| @@ -3,11 +3,34 @@ |
| package host |
| import ( |
| + "fmt" |
| "os" |
| "path/filepath" |
| "testing" |
| ) |
| +func TestPlan14GenerationKernelRetainedNamespaceMount(t *testing.T) { |
| + for _, fs := range []string{"tmpfs", "nsfs"} { |
| + t.Run(fs, func(t *testing.T) { |
| + f := generationProc(t) |
| + b := f.reservation.Binding |
| + mounts := fmt.Sprintf("1 0 %d:%d / / rw - tmpfs tmpfs rw\n", generationMajor(b.TreeIdentity.Device), generationMinor(b.TreeIdentity.Device)) |
| + // A namespace bind mount survives its last task and descriptor. |
| + // Its device differs from the release tree and its path names no build. |
| + mounts += fmt.Sprintf("2 1 0:999 / /retained-namespace rw shared:7 - %s %s rw\n", fs, fs) |
| + generationWrite(t, f.task+"/mountinfo", mounts) |
| + k := generationKernel{proc: f.proc} |
| + if _, err := k.absent(t.Context(), f.reservation); (err == nil) != (fs == "tmpfs") { |
| + t.Errorf("retirement with %s mount: %v", fs, err) |
| + } |
| + tree := filepath.Dir(filepath.Dir(b.Tree)) |
| + if _, _, err := k.census(t.Context(), tree); (err == nil) != (fs == "tmpfs") { |
| + t.Errorf("completeness with %s mount: %v", fs, err) |
| + } |
| + }) |
| + } |
| +} |
| + |
| func TestPlan14GenerationKernelCensusUnknownReference(t *testing.T) { |
| for _, attack := range []string{"none", "unreserved-executable", "unreserved-sibling", "uncontained", "namespace"} { |
| t.Run(attack, func(t *testing.T) { |
diff --git a/internal/host/plan14_generation_namespace_root_linux_test.go b/internal/host/plan14_generation_namespace_root_linux_test.go| new file mode 100644 |
| index 000000000..faef50642 |
| --- /dev/null |
| +++ b/internal/host/plan14_generation_namespace_root_linux_test.go |
| @@ -0,0 +1,226 @@ |
| +//go:build linux && rootrunner |
| + |
| +package host |
| + |
| +import ( |
| + "context" |
| + "encoding/json" |
| + "fmt" |
| + "io" |
| + "os" |
| + "os/exec" |
| + "path/filepath" |
| + "strings" |
| + "syscall" |
| + "testing" |
| + |
| + "github.com/carlosframework/platform/internal/hostbox" |
| + "github.com/carlosframework/platform/internal/release" |
| + "github.com/carlosframework/platform/internal/rootparticipation" |
| + "github.com/carlosframework/platform/internal/rootstate" |
| + "golang.org/x/sys/unix" |
| +) |
| + |
| +func TestPlan14GenerationKernelNamespaceMountRootRunner(t *testing.T) { |
| + const childKey = "PLAN14_E27_NAMESPACE_CHILD" |
| + dir := os.Getenv(childKey) |
| + if dir == "" { |
| + dir = generationRootRunnerDir(t) |
| + cmd := exec.CommandContext(t.Context(), "unshare", "--mount", "--pid", "--fork", "--mount-proc", os.Args[0], "-test.run=^TestPlan14GenerationKernelNamespaceMountRootRunner$", "-test.v", "-test.timeout=2m") |
| + cmd.Dir = dir |
| + cmd.Env = append(os.Environ(), childKey+"="+dir) |
| + if out, err := cmd.CombinedOutput(); err != nil { |
| + t.Fatalf("isolated namespace census: %v\n%s", err, out) |
| + } else { |
| + t.Logf("%s", out) |
| + } |
| + return |
| + } |
| + if os.Geteuid() != 0 { |
| + t.Fatal("designated runner must execute as root") |
| + } |
| + alias := filepath.Join(dir, "hidden-alias") |
| + tree := filepath.Join(dir, "versions") |
| + if os.Getenv("PLAN14_E27_NAMESPACE_HOLDER") == "1" { |
| + if err := unix.Mount("", "/", "", unix.MS_REC|unix.MS_PRIVATE, ""); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := unix.Mount(tree, alias, "", unix.MS_BIND, ""); err != nil { |
| + t.Fatal(err) |
| + } |
| + ready := os.NewFile(3, "ready") |
| + hold := os.NewFile(4, "hold") |
| + if _, err := ready.Write([]byte{1}); err != nil { |
| + t.Fatal(err) |
| + } |
| + ready.Close() |
| + if _, err := io.Copy(io.Discard, hold); err != nil { |
| + t.Fatal(err) |
| + } |
| + hold.Close() |
| + // Leave the bind in this private namespace for the parent's retained |
| + // namespace mount. The parent removes that final reference after checking. |
| + return |
| + } |
| + t.Setenv("CARLOS_OPT_DIR", dir) |
| + for _, path := range []string{tree, alias} { |
| + if err := os.Mkdir(path, 0755); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + if err := os.WriteFile(filepath.Join(tree, "artifact"), []byte("retained"), 0644); err != nil { |
| + t.Fatal(err) |
| + } |
| + pins, certificate := generationNamespacePinFixture(t, dir) |
| + if got, err := pins.StagedPins(t.Context(), dir); err != nil || len(got) != 0 { |
| + t.Fatalf("initial quiescent pins: %v %v", got, err) |
| + } |
| + initial := certificate() |
| + if !initial.Complete { |
| + t.Fatal("initial completeness was not recorded") |
| + } |
| + readyR, readyW, err := os.Pipe() |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + defer readyR.Close() |
| + defer readyW.Close() |
| + holdR, holdW, err := os.Pipe() |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + defer holdR.Close() |
| + defer holdW.Close() |
| + child := exec.CommandContext(t.Context(), os.Args[0], "-test.run=^TestPlan14GenerationKernelNamespaceMountRootRunner$", "-test.timeout=90s") |
| + child.Env = append(os.Environ(), "PLAN14_E27_NAMESPACE_HOLDER=1") |
| + child.Dir = dir |
| + child.ExtraFiles = []*os.File{readyW, holdR} |
| + child.SysProcAttr = &syscall.SysProcAttr{Cloneflags: unix.CLONE_NEWNS} |
| + child.Stdout, child.Stderr = os.Stdout, os.Stderr |
| + if err := child.Start(); err != nil { |
| + t.Fatal(err) |
| + } |
| + joined := false |
| + defer func() { |
| + if !joined { |
| + _ = child.Process.Kill() |
| + _ = child.Wait() |
| + } |
| + }() |
| + readyW.Close() |
| + holdR.Close() |
| + var ready [1]byte |
| + if _, err := io.ReadFull(readyR, ready[:]); err != nil || ready[0] != 1 { |
| + t.Fatalf("namespace holder did not establish the bind: %v", err) |
| + } |
| + namespace := filepath.Join(dir, "retained-namespace") |
| + if err := os.WriteFile(namespace, nil, 0600); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := unix.Mount(fmt.Sprintf("/proc/%d/ns/mnt", child.Process.Pid), namespace, "", unix.MS_BIND, ""); err != nil { |
| + t.Fatal(err) |
| + } |
| + mounted := true |
| + defer func() { |
| + if mounted { |
| + if err := unix.Unmount(namespace, 0); err != nil { |
| + t.Error(err) |
| + } |
| + } |
| + }() |
| + holdW.Close() |
| + err = child.Wait() |
| + joined = true |
| + if err != nil { |
| + t.Fatal("namespace holder did not exit cleanly", err) |
| + } |
| + mounts, err := os.ReadFile("/proc/self/mountinfo") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + found := false |
| + for _, line := range strings.Split(string(mounts), "\n") { |
| + if strings.Contains(line, " "+namespace+" ") && strings.Contains(line, " - nsfs ") { |
| + found = true |
| + t.Logf("retained namespace after child exit: %s", line) |
| + } |
| + } |
| + if !found { |
| + t.Fatal("fixture did not retain an nsfs mount") |
| + } |
| + retained := exec.CommandContext(t.Context(), "nsenter", "--mount="+namespace, "/bin/cat", "/proc/self/mountinfo") |
| + if out, err := retained.CombinedOutput(); err != nil || !strings.Contains(string(out), " "+alias+" ") { |
| + t.Fatalf("retained namespace lost the staged bind: %v\n%s", err, out) |
| + } |
| + if got, err := pins.StagedPins(t.Context(), dir); err == nil || got != nil { |
| + t.Fatalf("namespace retained by a mount granted pins: %v %v", got, err) |
| + } |
| + denied := certificate() |
| + if denied.Complete || denied.Revision <= initial.Revision { |
| + t.Fatal("namespace refusal left the previous certificate usable") |
| + } |
| + if err := unix.Unmount(namespace, 0); err != nil { |
| + t.Fatal(err) |
| + } |
| + mounted = false |
| + if got, err := pins.StagedPins(t.Context(), dir); err != nil || len(got) != 0 { |
| + t.Fatalf("released namespace still refused: %v %v", got, err) |
| + } |
| + final := certificate() |
| + if !final.Complete || final.Revision <= denied.Revision { |
| + t.Fatal("released namespace did not obtain a fresh certificate") |
| + } |
| +} |
| + |
| +func generationNamespacePinFixture(t *testing.T, dir string) (generationPinSource, func() release.GenerationCompleteness) { |
| + t.Helper() |
| + censusPath, fencesPath := filepath.Join(dir, "census"), filepath.Join(dir, "fences") |
| + journalPath, generationsPath := filepath.Join(dir, "journals.db"), filepath.Join(dir, "generations") |
| + for _, path := range []string{censusPath, fencesPath, generationsPath} { |
| + state, err := rootstate.Open(path, true, false) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if path == censusPath { |
| + raw, err := json.Marshal(rootparticipation.Census{Schema: 1, Box: "runner", Enrollment: "en", Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true}) |
| + if err == nil { |
| + err = state.Write("census.json", raw) |
| + } |
| + if err != nil { |
| + state.Close() |
| + t.Fatal(err) |
| + } |
| + } |
| + state.Close() |
| + } |
| + journal, err := hostbox.OpenLaunchJournal(journalPath) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + _, err = hostbox.NewJournalService(journal) |
| + journal.Close() |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + open := func() (rootstate.Store, error) { return rootstate.Open(generationsPath, false, false) } |
| + v := generationReconciler{ |
| + read: func(ctx context.Context) (generationInventory, error) { |
| + return generationInventoryAt(ctx, censusPath, fencesPath, journalPath) |
| + }, |
| + kernel: productionGenerationKernelCensus, |
| + } |
| + source := generationPinSource{open: open, retire: productionRetireGenerations, reconcile: v.reconcile} |
| + return source, func() release.GenerationCompleteness { |
| + t.Helper() |
| + state, err := open() |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + defer state.Close() |
| + c, err := (&release.RootGenerations{Store: state}).Completeness() |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + return c |
| + } |
| +} |
diff --git a/internal/host/rootgeneration_kernel_linux.go b/internal/host/rootgeneration_kernel_linux.go| index 524c4b140..eb3490bbe 100644 |
| --- a/internal/host/rootgeneration_kernel_linux.go |
| +++ b/internal/host/rootgeneration_kernel_linux.go |
| @@ -230,6 +230,11 @@ func generationMounts(raw []byte, b release.GenerationBinding) error { |
| if separator < 6 || len(f) != separator+4 { |
| return os.ErrPermission |
| } |
| + // A bind-mounted namespace can retain staged mounts after its last task |
| + // and descriptor disappear. Its nsfs device does not identify that tree. |
| + if f[separator+1] == "nsfs" { |
| + return os.ErrPermission |
| + } |
| major, minor, ok := strings.Cut(f[2], ":") |
| if !ok { |
| return os.ErrPermission |
diff --git a/internal/provision/testdata/plan14-root-reads-e.tsv b/internal/provision/testdata/plan14-root-reads-e.tsv| index 42cad1a09..a7f4b3c4a 100644 |
| --- a/internal/provision/testdata/plan14-root-reads-e.tsv |
| +++ b/internal/provision/testdata/plan14-root-reads-e.tsv |
| @@ -54,7 +54,7 @@ internal/release/roothistory_replace.go replacementReconstruction authority E1 S |
| internal/release/signedhead.go StageSignedHead authority E1 Canonical route is supplied by the root unit verifier. Verifies signed channel, digest ratchet and manifest/artifact before staging; unavailable pin census never authorizes restaging differing bytes. Fixtures: TestPlan14Incident20261006WithLedger and TestPlan14SignedNamedRecoveryAuthority. registry:Account@202,registry:Account@302,registry:Account@403,registry:App@214,registry:App@415,registry:Channel@222,registry:Host@434 ca02f04e55556ba0233dfa0848b0c533a9d9aa06cad6abd33cfd2968b3bd838a |
| internal/rootstate/link_linux.go ReadTrustedLink authority A,E1 Checks the full root-owned parent chain before descriptor-based live-link reading. Fixture: TestPlan14TrustedLiveLink; privileged acceptance remains required. file:Open@156 90f8e8de4e2c71f07b5d749cbe4bb1c1dfa426576a82138823449f56fc9ec5ab |
| internal/rootstate/link_linux.go directory.readLink authority A,E1 Pins the symlink inode with O_PATH and checks owner/type/link count before readlinkat. Fixture: TestPlan14TrustedLiveLink. file:Fstat@356,file:Openat@183,file:Readlinkat@554 a8559fe63dce48830916004ecfe7fbbe5d419568c7daf7a3073cf1b5bb12c3d7 |
| -@coverage internal/host 04670b9151cb86612fafacc8bdbe4bff1ab0324066d7914516d3317067c213d1 |
| +@coverage internal/host 5d14baaddbf17444552af2e28a1c98fa761ee81b2617e8a7db076fe961a18643 |
| @coverage internal/release 2bb546a86c1ab49b02e424eb0b366b46776857a1249baa2d6fb61ead4209d6bd |
| @coverage internal/rootstate 6338f74a6b2059cf77cf478bd6111b37b441eb9d65a309464c378359d4bdac05 |
| internal/release/roothistory_status.go RootHistory.MigrationStatus authority E1 Exact completion binds the accepted authorization digest from durable private history to enrolled-root signed readback. Pending originals, changed evidence and canonical sources cannot supply retirement completion. Fixtures: TestPlan14HistoryReconstructionCompletion, TestPlan14HistoryRetirementCompletion, TestPlan14ReadReconstructionCompletion, TestPlan14ReadRetirementCompletion, TestPlan14RecoveryRetirementProductionCallback, TestPlan14RootHistoryReconstructionCrash, TestPlan14HistoryRetirementProcessCrash. file:Read@1062,file:Read@3891 f0ab7ad2d2d70f8c22ebfd575f5a1c6931f7eba30ccdba80ad43d0efffc88154 |
| @@ -238,7 +238,7 @@ internal/host/rootgeneration_kernel_linux.go generationLinkRef authority E2 E21: |
| internal/host/rootgeneration_kernel_linux.go generationMajor authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. 48d037aef3349cd11492bb1cf7e9608f05bfde15e004f67f3bcad62a0518ab82 |
| internal/host/rootgeneration_kernel_linux.go generationMaps authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. 7549bed808c1431fb382c48cddde8d1730713aba73b59467ad0677901d72becc |
| internal/host/rootgeneration_kernel_linux.go generationMinor authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. 91033cac8c511dd135d42ec7f319333b50f545cb5a72c4d667b9d41db64f85be |
| -internal/host/rootgeneration_kernel_linux.go generationMounts authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. 4e96c33e01ca5c38ed736d5c66ec7b1227b6eafeff45729b05abd507ad13a95d |
| +internal/host/rootgeneration_kernel_linux.go generationMounts authority E2 E27: refuse nsfs mounts explicitly because a mounted namespace can retain staged bind mounts after its last process and descriptor exit. Fixtures: TestPlan14GenerationKernelRetainedNamespaceMount (retirement and census); TestPlan14GenerationKernelNamespaceMountRootRunner (real namespace retention and release, runner-pending). Other mount-device checks remain restrictive. 273d3034062da002130135b66312d547f680e5b0cd91834dc60cac3712262e91 |
| internal/host/rootgeneration_kernel_linux.go generationNames authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. file:Open@65,file:Readdirnames@149 1b3e2537c6d2f768d91517f2f7087bb2ff4a290f89c849e31b05de8b80ec9126 |
| internal/host/rootgeneration_kernel_linux.go generationPathRef authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. 1b980d5b7083aea4353446743ab723f8008963d6504d480c8898b7a0d0d94549 |
| internal/host/rootgeneration_kernel_linux.go generationRead authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. file:Open@62,file:ReadAll@144 6ccd7554fc1f5683c585eb4fd3613d8f2e834d5dfb6c77e4732cb003d1e53065 |