//go:build linux package host import ( "context" "errors" "io/fs" "os" "path/filepath" "slices" "strconv" "strings" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/instancectl" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rootstate" ) func isolatedGeneration(r release.GenerationReservation) bool { return (r.Binding.Unit == "" || r.Binding.Unit == instancectl.UnitName(instancectl.RoleRun, r.Binding.Target.Host)) && (r.Process == (release.GenerationProcess{}) || r.Process.Containment == 1) } func retiredGenerationReceipt(ctx context.Context, view hostguard.FenceView, owner release.GenerationOwner) error { if view.Host != owner.Host || view.Barrier != nil || !slices.Contains(view.Retired, hostguard.Captured{Incarnation: owner.Incarnation, Stamp: owner.Stamp}) { return os.ErrPermission } for _, receipt := range view.Receipts { if generationOwner(receipt.Target) == owner && verifyRetirementReceipt(ctx, receipt) == nil { return nil } } return os.ErrPermission } func productionRetiredGenerationAbsence(ctx context.Context, r release.GenerationReservation, view hostguard.FenceView) (string, error) { if r.Binding.Path != release.VersionPath(release.OptDir(), r.Binding.Binding.Account, r.Binding.Version, r.Binding.Binding.App) { return "", os.ErrPermission } if !isolatedGeneration(r) { return productionGenerationAbsence(ctx, r) } t := r.Binding.Target if !slices.Contains(view.Retired, hostguard.Captured{Incarnation: t.Incarnation, Stamp: t.Stamp}) { return productionGenerationAbsence(ctx, r) } if err := retiredGenerationReceipt(ctx, view, t); err != nil { return "", err } if os.Geteuid() != 0 { return "", os.ErrPermission } if err := verifyStopKernelMounts("/sys/fs/cgroup", "/proc"); err != nil { return "", err } current, err := generationRecordedTargets(ctx, r) if err != nil { return "", err } if err := generationRetiredController(ctx, current.Binding); err != nil { return "", err } boot, err := (generationKernel{proc: "/proc"}).boot() if err != nil { return "", err } return generationInventoryDigest([]any{"retired-isolated", current, boot, view.Receipts}), nil } func retiredGenerationSnapshot(ctx context.Context, inv generationInventory, records []release.GenerationReservation) bool { if len(inv.Installations) == 0 || len(inv.Launches) == 0 { return false } views := map[string]hostguard.FenceView{} all := map[string]hostguard.FenceView{} for _, view := range inv.Installations { in := view.Installation all[view.Host] = view if view.Barrier != nil { return false } if !slices.Contains(view.Retired, hostguard.Captured{Incarnation: in.Incarnation, Stamp: in.Stamp}) { continue } views[view.Host] = view } if len(views) == 0 { return false } for _, r := range records { if _, ok := all[r.Binding.Target.Host]; !ok { return false } if _, retired := views[r.Binding.Target.Host]; !retired { if !isolatedGeneration(r) { return false } continue } if r.Retirement == "" || !r.Closed || !isolatedGeneration(r) || retiredGenerationReceipt(ctx, views[r.Binding.Target.Host], r.Binding.Target) != nil { return false } } for _, launch := range inv.Launches { if _, ok := all[launch.Target.Host]; !ok { return false } if _, retired := views[launch.Target.Host]; !retired { continue } if !launch.WritersClosed || !launch.Completed.Valid() || retiredGenerationReceipt(ctx, views[launch.Target.Host], generationOwner(launch.Target)) != nil { return false } for _, r := range launch.Reservations { if !hostbox.IsUnitReservation(r) || r.State != hostbox.UnitSettled || !hostbox.SameLaunchOwner(r.Target, launch.Target) { return false } matches := 0 for _, pin := range records { if pin.Binding.Target == generationOwner(launch.Target) && pin.Binding.Attempt == r.ID { matches++ } } if matches != 1 { return false } } } return true } func emptyGenerationGroups(ctx context.Context, root string, active ...release.GenerationReservation) error { return (generationKernel{proc: "/proc", cgroups: "/sys/fs/cgroup", contained: active}).emptyGroups(ctx, root) } func (k generationKernel) emptyGroups(ctx context.Context, root string) error { count := 0 return filepath.WalkDir(root, func(path string, entry fs.DirEntry, err error) error { if path == root && errors.Is(err, os.ErrNotExist) { return nil } if err != nil { return err } if err := ctx.Err(); err != nil { return err } count++ if count > 8192 || entry.Type()&os.ModeSymlink != 0 { return os.ErrPermission } if !entry.IsDir() { return nil } raw, err := os.ReadFile(filepath.Join(path, "cgroup.procs")) if err != nil { return err } for _, pid := range strings.Fields(string(raw)) { if _, err := strconv.ParseUint(pid, 10, 32); err != nil { return os.ErrPermission } task := filepath.Join(k.proc, pid) status, err := generationRead(task + "/status") if err != nil { return err } ok, err := k.containedTask(ctx, task, status) if err != nil { return err } if !ok { return os.ErrPermission } } return nil }) } func productionRetiredGenerationKernelCensus(ctx context.Context) (string, string, error) { records, ok := ctx.Value(generationCensusReservationsKey{}).([]release.GenerationReservation) if !ok { return "", "", os.ErrPermission } inv, err := productionLocalGenerationInventory(ctx) if err != nil { return "", "", err } if !retiredGenerationSnapshot(ctx, inv, records) { return productionGenerationKernelCensus(ctx) } if os.Geteuid() != 0 { return "", "", os.ErrPermission } if err := verifyStopKernelMounts("/sys/fs/cgroup", "/proc"); err != nil { return "", "", err } k := generationKernel{proc: "/proc"} boot, err := k.boot() if err != nil { return "", "", err } for _, launch := range inv.Launches { retired := false for _, view := range inv.Installations { if view.Host == launch.Target.Host && slices.Contains(view.Retired, hostguard.Captured{Incarnation: launch.Target.Incarnation, Stamp: launch.Target.Stamp}) { retired = true } } if !retired { continue } for _, r := range records { if r.Binding.Target == generationOwner(launch.Target) { if err := generationRetiredController(ctx, r.Binding); err != nil { return "", "", err } } } for _, group := range launch.Groups { if !filepath.IsLocal(group.RelativePath) || group.RelativePath == "." || filepath.Clean(group.RelativePath) != group.RelativePath { return "", "", os.ErrPermission } if err := emptyGenerationGroups(ctx, filepath.Join("/sys/fs/cgroup", group.RelativePath)); err != nil { return "", "", err } } } if err := emptyGenerationGroups(ctx, "/sys/fs/cgroup/carlos.slice/carlos-instances.slice", records...); err != nil { return "", "", err } if err := checkedGenerationTree(ctx, filepath.Join(release.OptDir(), "versions")); err != nil { return "", "", err } after, err := k.boot() if err != nil || after != boot { return "", "", os.ErrPermission } return boot, generationInventoryDigest([]any{"retired-isolated-census", inv, records, boot}), nil } func generationRetiredController(ctx context.Context, b release.GenerationBinding) error { service, recheck := generationServiceReader(ctx) k := generationKernel{proc: "/proc", service: service, recheck: recheck} if k.controllerGone(ctx, b) == nil { return nil } boot, err := k.boot() if err != nil || boot != b.Target.BootID { return os.ErrPermission } path := filepath.Join("/proc", strconv.Itoa(b.ControllerPID)) start, err := claimLaunchProcessStart("/proc", b.ControllerPID) if err != nil || start != b.ControllerStart { return os.ErrPermission } b, err = generationCurrentFiles(b, rootstate.OpenTrustedFile) if err != nil { return err } refs, err := generationTree(ctx, b) if err != nil { return err } tasks, err := generationNames(path + "/task") if err != nil { return err } for _, tid := range tasks { if _, err := k.task(ctx, path+"/task/"+tid, b, nil, refs); err != nil { return err } } again, err := claimLaunchProcessStart("/proc", b.ControllerPID) if err != nil || again != start { return os.ErrPermission } return nil }