package host import ( "context" "encoding/json" "errors" "net" "os" "os/exec" "path/filepath" "strconv" "strings" "time" "github.com/carlosframework/platform/internal/rootstate" "github.com/carlosframework/platform/internal/safefile" "golang.org/x/sys/unix" ) func captureClaimUnitSocket(path, boot string) (claimUnitSocketIdentity, error) { parent, err := safefile.OpenDir(filepath.Dir(path)) if err != nil { return claimUnitSocketIdentity{}, err } defer parent.Close() return claimUnitSocketAt(int(parent.Fd()), filepath.Base(path), boot) } func claimUnitSocketAt(parent int, name, boot string) (claimUnitSocketIdentity, error) { var st unix.Stat_t if err := unix.Fstatat(parent, name, &st, unix.AT_SYMLINK_NOFOLLOW); err != nil { return claimUnitSocketIdentity{}, err } if boot == "" || st.Mode&unix.S_IFMT != unix.S_IFSOCK || st.Nlink != 1 { return claimUnitSocketIdentity{}, os.ErrPermission } return claimUnitSocketIdentity{Boot: boot, Device: uint64(st.Dev), Inode: st.Ino, UID: st.Uid, GID: st.Gid, Mode: st.Mode}, nil } func parkClaimUnitSocket(source, archive, boot string, want claimUnitSocketIdentity) error { return parkClaimUnitSocketWith(source, archive, boot, want, unix.Renameat2) } func parkClaimUnitSocketWith(source, archive, boot string, want claimUnitSocketIdentity, rename func(int, string, int, string, uint) error) error { if want.Boot == "" || want.Device == 0 || want.Inode == 0 || want.Mode&unix.S_IFMT != unix.S_IFSOCK || boot == "" { return os.ErrPermission } src, err := safefile.OpenDir(filepath.Dir(source)) if err != nil { return err } defer src.Close() dst, err := safefile.OpenDir(filepath.Dir(archive)) if err != nil { return err } defer dst.Close() sfd, dfd := int(src.Fd()), int(dst.Fd()) sname, dname := filepath.Base(source), filepath.Base(archive) old, sourceErr := claimUnitSocketAt(sfd, sname, boot) parked, archiveErr := claimUnitSocketAt(dfd, dname, boot) if errors.Is(sourceErr, os.ErrNotExist) { if errors.Is(archiveErr, os.ErrNotExist) { return errors.Join(src.Sync(), dst.Sync()) } if archiveErr != nil || parked != want { return os.ErrPermission } return errors.Join(src.Sync(), dst.Sync()) } if sourceErr != nil || old != want || !errors.Is(archiveErr, os.ErrNotExist) { return os.ErrPermission } if err = rename(sfd, sname, dfd, dname, unix.RENAME_NOREPLACE); err != nil { return err } parked, err = claimUnitSocketAt(dfd, dname, boot) if err != nil || parked != want { // Preserve a raced replacement; never overwrite a new source name. restore := unix.Renameat2(dfd, dname, sfd, sname, unix.RENAME_NOREPLACE) return errors.Join(os.ErrPermission, err, restore, src.Sync(), dst.Sync()) } return errors.Join(src.Sync(), dst.Sync()) } func captureProductionClaimUnitSocket(ctx context.Context, s claimUnitShape, p claimUnitPredecessor) (claimUnitSocketIdentity, error) { if !claimFlatSocket(s) { return claimUnitSocketIdentity{}, os.ErrPermission } boot, err := claimUnitBoot() if err != nil { return claimUnitSocketIdentity{}, err } before, err := captureClaimUnitSocket(s.Socket, boot) if err != nil { return before, err } out, err := exec.CommandContext(ctx, "systemctl", "show", "-p", "MainPID", "--value", p.Unit).Output() if err != nil { return before, err } pid, err := strconv.Atoi(strings.TrimSpace(string(out))) if err != nil || pid <= 1 { return before, os.ErrPermission } conn, err := (&net.Dialer{Timeout: time.Second}).DialContext(ctx, "unix", s.Socket) if err != nil { return before, err } defer conn.Close() if err = claimProbePeer(conn, pid, "0::/"+p.Group); err != nil { return before, err } after, err := captureClaimUnitSocket(s.Socket, boot) if err != nil || after != before { return before, os.ErrPermission } return before, nil } func parkProductionClaimUnitSocket(b claimUnitBinding, id claimUnitSocketIdentity) error { if !b.valid() || !claimFlatSocket(b.Shape) { return os.ErrPermission } boot, err := claimUnitBoot() if err != nil { return err } const dir = "/run/carlos-control/unit-migrations" store, err := rootstate.Open(dir, true, false) if err != nil { return err } defer store.Close() raw, err := json.Marshal(b) if err != nil { return err } return parkClaimUnitSocket(b.Shape.Socket, filepath.Join(dir, claimUnitDigest(raw)+".sock"), boot, id) }