carlos / platform Public

WIP claim Plan 14 track E checkpoint 27 (not for merge)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev e3d87f728659fcc2a73d367b92d7d45b1295dc2d parent a323d96
4 files changed, +256 −2
  • internal/host/plan14_generation_census_kernel_linux_test.go +23 −0
  • internal/host/plan14_generation_namespace_root_linux_test.go +226 −0
  • internal/host/rootgeneration_kernel_linux.go +5 −0
  • internal/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