//go:build linux package host import ( "context" "errors" "os" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostguard" "golang.org/x/sys/unix" ) // The parent is established through a root-validated installation before any // namespace mutation. Startup can retain an obstruction using this evidence // even after the originating host has gone; it grants no artifact deletion. type servingParkParent struct { BootID string `json:",omitempty"` Version int Parent servingArtifactDirectory } func (f *servingParkFS) namespaceParent() (servingParkParent, error) { p, err := f.historicalNamespaceParent() if err != nil { return p, err } b, err := servingParkReadBinding(f.state, "namespace") if err != nil || b == nil { return p, err } if p.BootID != b.BeforeBoot || p.Parent != b.BeforeParent { return p, hostbox.ErrChanged } p.BootID, p.Parent = b.BootID, b.Parent return p, nil } func (f *servingParkFS) historicalNamespaceParent() (servingParkParent, error) { var p servingParkParent b, err := f.state.Read("namespace-parent.json") if err != nil { return p, err } if servingParkDecode(b, &p) != nil || p.Version != 1 || p.Parent.Device == 0 || p.Parent.Inode == 0 || p.Parent.Mount == 0 { return p, hostbox.ErrChanged } return p, nil } // The caller holds namespace and host locks and has checked the claim, root // installation, inventory and stop evidence. Directory is never a wire path. func (f *servingParkFS) recordNamespaceParent(ctx context.Context, current servingArtifactIdentity, directory servingArtifactDirectory) error { if err := ctx.Err(); err != nil { return err } boot, err := f.currentBoot() if err != nil { return err } source, expected, err := f.source(servingParkAuthorization{Successor: current, Directory: directory}) if err != nil { return err } defer source.Close() parent, actual, err := f.parent() if err != nil { return err } defer parent.Close() var st unix.Stat_t if err = unix.Fstat(int(parent.Fd()), &st); err != nil { return err } if actual != expected || st.Mode&0022 != 0 { return hostbox.ErrChanged } p, err := f.namespaceParent() if err == nil { if p.Parent != expected || f.checkBoot(p.BootID) != nil { return hostbox.ErrChanged } return nil } if !errors.Is(err, os.ErrNotExist) { return err } // Existing private records constrain a first parent capture too. A new // authenticated host cannot bless a replacement of an older mount parent. for _, name := range []string{"namespace.json", "namespace-quarantine.json"} { body, err := f.state.Read(name) if errors.Is(err, os.ErrNotExist) { continue } if err != nil { return err } if name == "namespace.json" { var n servingParkNamespace if servingParkDecode(body, &n) != nil || n.Version != 1 || n.Parent != expected || n.Root.Inode == 0 || n.Root.Device != expected.Device || n.Root.Mount != expected.Mount || f.checkBoot(n.BootID) != nil { return hostbox.ErrChanged } } else { var q servingParkQuarantine if servingParkDecode(body, &q) != nil || q.validate() != nil || q.Parent != expected || f.checkBoot(q.BootID) != nil { return hostbox.ErrChanged } } } if err = ctx.Err(); err != nil { return err } return (&servingParkLedger{state: f.state}).write("namespace-parent.json", servingParkParent{Version: 1, Parent: expected, BootID: boot}) } // Called under the namespace lock. Absence of parent evidence never permits // recovery from a pathname alone. A normal empty installation needs no work. func (f *servingParkFS) recoverRecordedNamespace(ctx context.Context) error { if err := ctx.Err(); err != nil { return err } p, err := f.namespaceParent() if errors.Is(err, os.ErrNotExist) { return nil } if err != nil { return err } if err := f.checkBoot(p.BootID); err != nil { return err } parent, id, err := f.parent() if err != nil { return err } defer parent.Close() var st unix.Stat_t if err = unix.Fstat(int(parent.Fd()), &st); err != nil { return err } if id != p.Parent || st.Mode&0022 != 0 { return hostbox.ErrChanged } // An unfinished quarantine must replay before ordinary namespace opening. if _, err = f.state.Read("namespace-quarantine.json"); err == nil { _, err = f.recoverNamespace(ctx, p.Parent) return err } else if !errors.Is(err, os.ErrNotExist) { return err } n, err := f.readNamespace() if err == nil { if n.Version != 1 || n.Parent != p.Parent || n.Root.Inode == 0 || n.Root.Device != id.Device || n.Root.Mount != id.Mount || f.checkBoot(n.BootID) != nil { return hostbox.ErrChanged } root, rid, err := f.privateDir(parent, ".serving-parked") if err == nil { root.Close() if rid == n.Root { return nil } } } else if errors.Is(err, os.ErrNotExist) { root, err := servingParkOpenDir(parent, ".serving-parked") if errors.Is(err, os.ErrNotExist) { return nil } if err != nil { return err } root.Close() } else { return err } _, err = f.recoverNamespace(ctx, p.Parent) return err } func (p *servingParkRoot) prepareServingNamespace(ctx context.Context, a servingParkAuthorization, receipts []hostguard.FenceReceipt, legacy *servingLegacyEvidence) error { fs, ok := p.ledger.fs.(*servingParkFS) if !ok { return hostbox.ErrUnsupported } if _, err := p.ledger.read(a.ID); errors.Is(err, os.ErrNotExist) { if err := fs.room(); err != nil { return err } if _, err := p.ledger.capture(ctx, a, receipts, legacy); err != nil { return err } } else if err != nil { return err } if err := fs.recordNamespaceParent(ctx, a.Successor, a.Directory); err != nil { return err } return fs.recoverRecordedNamespace(ctx) }