carlos / platform Public

guard-arm: make the guard directories 0755 whatever the caller's umask

A umask 077 operator shell left /opt/carlos/system/guard and
guard/identities 0700 on the dev console box, and console.service (run as
carlos, exec'ing the guard binary) restart-looped with exit 203.
mkdirAllExact chmods every directory it creates, and the target, to the
exact mode, so a re-run also repairs a box left at 0700. Census overlay
claim-guard-umask, ruled by platform-1e.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev b01ba4d6620ffed899b28c0eb60c260591fd5275 parent f3bf121
4 files changed, +92 −1
  • internal/host/guardarm.go +32 −1
  • internal/host/guardarm_mkdir_linux_test.go +47 −0
  • internal/provision/plan14_root_reads_test.go +8 −0
  • internal/provision/testdata/plan14-root-reads-claim-guard-umask.tsv +5 −0
diff --git a/internal/host/guardarm.go b/internal/host/guardarm.go
index 98e4c7693..f0d09bc37 100644
--- a/internal/host/guardarm.go
+++ b/internal/host/guardarm.go
@@ -111,8 +111,12 @@ func RunSystemGuardArm(args []string) {
if err = guardArmControllers(dataDir()); err != nil {
log.Fatal(err)
}
+ // The guard directories are traversed by non-root units (the console
+ // and the edge exec the guard binary through them), so their mode
+ // cannot depend on the caller's umask: a umask 077 shell once left
+ // them 0700 and the guarded console failed with exit 203.
for _, dir := range []string{d.Dir, filepath.Join(d.Dir, "identities")} {
- if err = os.MkdirAll(dir, 0755); err != nil {
+ if err = mkdirAllExact(dir, 0755); err != nil {
log.Fatal(err)
}
}
@@ -269,3 +273,30 @@ func guardArmControllers(dataDir string) error {
}
return hostfloor.EnsureControllerDir(dataDir, uid, gid)
}
+
+// mkdirAllExact is os.MkdirAll with the caller's umask taken out: every
+// directory it creates, and dir itself whether or not it was created, ends
+// with exactly mode. Existing parents keep their modes.
+func mkdirAllExact(dir string, mode os.FileMode) error {
+ var created []string
+ for p := filepath.Clean(dir); ; p = filepath.Dir(p) {
+ if _, err := os.Lstat(p); err == nil {
+ break
+ } else if !os.IsNotExist(err) {
+ return err
+ }
+ created = append(created, p)
+ if parent := filepath.Dir(p); parent == p {
+ break
+ }
+ }
+ if err := os.MkdirAll(dir, mode); err != nil {
+ return err
+ }
+ for _, p := range append(created, filepath.Clean(dir)) {
+ if err := os.Chmod(p, mode); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/internal/host/guardarm_mkdir_linux_test.go b/internal/host/guardarm_mkdir_linux_test.go
new file mode 100644
index 000000000..906be977a
--- /dev/null
+++ b/internal/host/guardarm_mkdir_linux_test.go
@@ -0,0 +1,47 @@
+//go:build linux
+
+package host
+
+import (
+ "os"
+ "path/filepath"
+ "syscall"
+ "testing"
+)
+
+// A umask 077 operator shell must not leave the guard directories closed to
+// the non-root units that exec the guard binary through them.
+func TestMkdirAllExactIgnoresUmask(t *testing.T) {
+ old := syscall.Umask(0o077)
+ defer syscall.Umask(old)
+ root := t.TempDir()
+ if err := os.Chmod(root, 0o700); err != nil {
+ t.Fatal(err)
+ }
+ dir := filepath.Join(root, "system", "guard", "identities")
+ if err := mkdirAllExact(dir, 0o755); err != nil {
+ t.Fatal(err)
+ }
+ for _, p := range []string{filepath.Join(root, "system"), filepath.Join(root, "system", "guard"), dir} {
+ st, err := os.Stat(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if st.Mode().Perm() != 0o755 {
+ t.Fatalf("%s is %o, want 755", p, st.Mode().Perm())
+ }
+ }
+ if st, _ := os.Stat(root); st.Mode().Perm() != 0o700 {
+ t.Fatalf("an existing parent's mode changed: %o", st.Mode().Perm())
+ }
+ // A directory left 0700 by an earlier run is repaired.
+ if err := os.Chmod(dir, 0o700); err != nil {
+ t.Fatal(err)
+ }
+ if err := mkdirAllExact(dir, 0o755); err != nil {
+ t.Fatal(err)
+ }
+ if st, _ := os.Stat(dir); st.Mode().Perm() != 0o755 {
+ t.Fatalf("existing dir not repaired: %o", st.Mode().Perm())
+ }
+}
diff --git a/internal/provision/plan14_root_reads_test.go b/internal/provision/plan14_root_reads_test.go
index 13323fdb4..8e68b7dbb 100644
--- a/internal/provision/plan14_root_reads_test.go
+++ b/internal/provision/plan14_root_reads_test.go
@@ -925,6 +925,14 @@ func TestPlan14RootReadCensus(t *testing.T) {
if err = loadRootReadInventory(filepath.Join(root, "internal/provision/testdata/plan14-root-reads-claim-untagged-pointer-fix2.tsv"), &inv, true); err != nil {
t.Fatal(err)
}
+ // Census overlay claim-guard-umask (guard-arm's directories ignore the
+ // caller's umask), loaded last, under census-ruling-claim-guard-umask
+ // (platform-1e): RunSystemGuardArm re-pinned at its class F,J4, the new
+ // mkdirAllExact classified G,J4, and the internal/host envelope
+ // re-pinned. It adds no accepted-legacy row, so the seal stands.
+ if err = loadRootReadInventory(filepath.Join(root, "internal/provision/testdata/plan14-root-reads-claim-guard-umask.tsv"), &inv, true); err != nil {
+ t.Fatal(err)
+ }
seal = "53b3cbb6f46d657ce32f49dad84dae79316c80da949ccf37273833eef5c5b2d7"
errs := append(compareRootReads(got, inv), checkRootReadLegacy(inv, seal)...)
errs = append(errs, checkRootReadDeclarations(got, inv)...)
diff --git a/internal/provision/testdata/plan14-root-reads-claim-guard-umask.tsv b/internal/provision/testdata/plan14-root-reads-claim-guard-umask.tsv
new file mode 100644
index 000000000..d6ba8d084
--- /dev/null
+++ b/internal/provision/testdata/plan14-root-reads-claim-guard-umask.tsv
@@ -0,0 +1,5 @@
+# Census overlay claim-guard-umask (guard-arm's directories ignore the caller's umask; amadan issue drafted 2026-10-19), loaded LAST, after claim-untagged-pointer-fix2. Ruled by platform-1e as census owner: census-ruling-claim-guard-umask.
+# RunSystemGuardArm: the guard and identities directories are now made through mkdirAllExact instead of os.MkdirAll; its read sites are the same files at shifted offsets. mkdirAllExact (new): Lstat walks up only to find which components it creates, then chmods exactly those and the target to 0755. It reads no file content and grants nothing. The seal stands: no accepted-legacy row. No 9B8 entry: it writes no record.
+@coverage internal/host eb14b3eea2f1fb467aa899a71621e30404cfd1424f030fa320b34a04ebb481c1
+internal/host/guardarm.go RunSystemGuardArm authority F,J4 rev3=census-ruling-claim-guard-umask;identity=carlos system guard-arm, offline CLI uid 0;basis=root guard-arm checks retained census floor before installing guard; no census retains main; policy and identity parse in guardArmInputs, same files read; guard directories made through mkdirAllExact file:OpenFile@565,file:ReadFile@1572,file:ReadFile@5883,file:ReadFile@865,file:ReadFile@945,file:Stat@3534 a6bd0a2da1b5e4d19f3c58da8331dbc5e166da31aba289c77153afe4f28a4cbb
+internal/host/guardarm.go mkdirAllExact authority G,J4 rev3=census-ruling-claim-guard-umask;identity=carlos system guard-arm, offline CLI uid 0;basis=Lstat of the target path and its missing parents only, to choose which directories to create and chmod to 0755; no file content is read file:Lstat@149 d1c988f22c5559ba6765ba9072d3fbbf26fd33836ab672157cb4e1bd9b99aaf3