carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package host
4
5import (
6 "context"
7 "net"
8 "net/url"
9 "os"
10 "os/exec"
11 "path/filepath"
12 "strconv"
13 "strings"
14
15 "github.com/carlosframework/platform/internal/hostbox"
16 "github.com/carlosframework/platform/internal/hostwrite"
17 "github.com/carlosframework/platform/internal/registry"
18 "golang.org/x/sys/unix"
19)
20
21// claimProbeDepth bounds the parent walk from a peer to the spawned process.
22const claimProbeDepth = 64
23
24// claimProbeSocketReady is the claim's probe readiness check (claim Plan 9
25// D3; moves §7.7 as ruled in H12): GET the declared readiness path over the
26// route's socket and accept 2xx only, and only from a peer whose pid is the
27// spawned process or a descendant of it and is a member of exactly the main
28// role's cgroup. A preforking server's worker passes; any process outside the
29// spawned tree or the role's group never receives the request.
30func claimProbeSocketReady(ctx context.Context, cmd *exec.Cmd, rt registry.Route, in ClaimLaunchInputs, roleCgroup string) error {
31 target, ok := claimReadyURL(in.ReadyPath)
32 if cmd == nil || cmd.Process == nil || cmd.Process.Pid <= 1 || rt.Socket == "" || !ok || !filepath.IsAbs(roleCgroup) {
33 return hostwrite.ErrUnavailable
34 }
35 rel, err := filepath.Rel("/sys/fs/cgroup", filepath.Clean(roleCgroup))
36 if err != nil || !filepath.IsLocal(rel) || rel == "." {
37 return hostwrite.ErrUnavailable
38 }
39 root, member := cmd.Process.Pid, "0::/"+filepath.ToSlash(rel)
40 return claimPollReady(ctx, rt.Socket, target, func(conn net.Conn) error { return claimProbePeer(conn, root, member) }, func(status int) bool { return status >= 200 && status < 300 })
41}
42
43// claimReadyURL is the request target for a declared absolute path on the
44// socket's fixed host. A path that could name another host is refused.
45func claimReadyURL(path string) (string, bool) {
46 if !strings.HasPrefix(path, "/") || strings.HasPrefix(path, "//") || strings.ContainsAny(path, "#\\ \t\r\n\x00") {
47 return "", false
48 }
49 u, err := url.Parse("http://carlos" + path)
50 if err != nil || u.Host != "carlos" || u.User != nil || u.Opaque != "" {
51 return "", false
52 }
53 return u.String(), true
54}
55
56// claimProbePeer accepts the connection's peer only when its parent chain
57// reaches root and its cgroup v2 membership is exactly member.
58func claimProbePeer(conn net.Conn, root int, member string) error {
59 unixConn, ok := conn.(*net.UnixConn)
60 if !ok || root <= 1 {
61 return hostwrite.ErrUnavailable
62 }
63 raw, err := unixConn.SyscallConn()
64 if err != nil {
65 return err
66 }
67 var peer *unix.Ucred
68 var socketErr error
69 if err = raw.Control(func(fd uintptr) { peer, socketErr = unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED) }); err != nil {
70 return err
71 }
72 if socketErr != nil {
73 return socketErr
74 }
75 if peer == nil || peer.Pid <= 1 {
76 return hostwrite.ErrUnavailable
77 }
78 pid := int(peer.Pid)
79 for at, steps := pid, 0; at != root; steps++ {
80 if at <= 1 || steps >= claimProbeDepth {
81 return hostbox.ErrChanged
82 }
83 if at, err = claimParentPID(at); err != nil {
84 return err
85 }
86 }
87 groups, err := os.ReadFile("/proc/" + strconv.Itoa(pid) + "/cgroup")
88 if err != nil {
89 return err
90 }
91 for _, line := range strings.Split(string(groups), "\n") {
92 if line == member {
93 return nil
94 }
95 }
96 return hostbox.ErrChanged
97}
98
99func claimParentPID(pid int) (int, error) {
100 raw, err := os.ReadFile("/proc/" + strconv.Itoa(pid) + "/stat")
101 if err != nil {
102 return 0, err
103 }
104 i := strings.LastIndexByte(string(raw), ')')
105 if i < 0 {
106 return 0, hostwrite.ErrUnavailable
107 }
108 fields := strings.Fields(string(raw)[i+1:])
109 if len(fields) < 2 {
110 return 0, hostwrite.ErrUnavailable
111 }
112 return strconv.Atoi(fields[1])
113}
114