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 "os"
8 "path/filepath"
9 "strconv"
10 "strings"
11
12 "github.com/carlosframework/platform/internal/release"
13 "github.com/carlosframework/platform/internal/rootstate"
14 "golang.org/x/sys/unix"
15)
16
17func generationIdentity(path string) (rootstate.FileIdentity, error) {
18 var st unix.Stat_t
19 if err := unix.Stat(path, &st); err != nil {
20 return rootstate.FileIdentity{}, err
21 }
22 return rootstate.FileIdentity{Device: uint64(st.Dev), Inode: st.Ino}, nil
23}
24
25func (k generationKernel) containedTask(ctx context.Context, path string, status []byte) (bool, error) {
26 raw, err := generationRead(path + "/cgroup")
27 if err != nil {
28 return false, err
29 }
30 group := strings.TrimSpace(string(raw))
31 for _, r := range k.contained {
32 p := r.Process
33 if p.Containment != 1 || r.Closed || r.Retirement != "" || group != "0::/"+p.Group {
34 continue
35 }
36 if err := generationContainedStatus(status); err != nil {
37 return false, err
38 }
39 if err := k.containedBinding(ctx, path, r); err != nil {
40 return false, err
41 }
42 return true, nil
43 }
44 return false, nil
45}
46
47func generationContainedStatus(raw []byte) error {
48 fields := map[string]string{}
49 for _, line := range strings.Split(string(raw), "\n") {
50 key, value, ok := strings.Cut(line, ":")
51 if ok {
52 if _, dup := fields[key]; dup {
53 return os.ErrPermission
54 }
55 fields[key] = strings.TrimSpace(value)
56 }
57 }
58 for _, key := range []string{"CapInh", "CapPrm", "CapEff", "CapBnd", "CapAmb"} {
59 n, err := strconv.ParseUint(fields[key], 16, 64)
60 if err != nil || n != 0 {
61 return os.ErrPermission
62 }
63 }
64 if fields["NoNewPrivs"] != "1" || fields["Seccomp"] != "2" {
65 return os.ErrPermission
66 }
67 return nil
68}
69
70func (k generationKernel) containedBinding(ctx context.Context, path string, r release.GenerationReservation) error {
71 if err := ctx.Err(); err != nil {
72 return err
73 }
74 b, p := r.Binding, r.Process
75 boot, err := k.boot()
76 if err != nil || boot != b.Target.BootID || p.Group != generationBindingGroup(b) {
77 return os.ErrPermission
78 }
79 var group unix.Stat_t
80 if err := unix.Lstat(filepath.Join(k.cgroups, p.Group), &group); err != nil {
81 return err
82 }
83 if group.Uid != k.owner || group.Mode&0022 != 0 || group.Mode&unix.S_IFMT != unix.S_IFDIR || (rootstate.FileIdentity{Device: uint64(group.Dev), Inode: group.Ino}) != p.GroupIdentity {
84 return os.ErrPermission
85 }
86 main, tenant := filepath.Join(k.proc, strconv.Itoa(p.PID)), filepath.Join(k.proc, strconv.Itoa(p.TenantPID))
87 for pid, want := range map[int]string{p.PID: p.Start, p.TenantPID: p.TenantStart} {
88 start, err := claimLaunchProcessStart(k.proc, pid)
89 if err != nil || start != want {
90 return os.ErrPermission
91 }
92 raw, err := generationRead(filepath.Join(k.proc, strconv.Itoa(pid), "cgroup"))
93 if err != nil || strings.TrimSpace(string(raw)) != "0::/"+p.Group {
94 return os.ErrPermission
95 }
96 }
97 launcher, err := generationIdentity(main + "/exe")
98 if err != nil || launcher != p.LauncherIdentity {
99 return os.ErrPermission
100 }
101 privatePID := ""
102 for _, ns := range []string{"mnt", "pid"} {
103 want, err := os.Readlink(tenant + "/ns/" + ns)
104 if err != nil || want == "" {
105 return os.ErrPermission
106 }
107 if ns == "pid" {
108 privatePID = want
109 }
110 outer, err := os.Readlink(k.proc + "/self/ns/" + ns)
111 if err != nil || outer == want {
112 return os.ErrPermission
113 }
114 for _, task := range []string{main, path} {
115 actual, err := os.Readlink(task + "/ns/" + ns)
116 if err != nil || actual != want {
117 return os.ErrPermission
118 }
119 }
120 }
121 bound := strings.TrimPrefix(k.boundApp, "/")
122 if bound == "" {
123 bound = "opt/carlos/app"
124 }
125 id, err := generationIdentity(filepath.Join(path, "root", bound))
126 if err != nil || id != b.Executable {
127 return os.ErrPermission
128 }
129 tree := filepath.Dir(b.Tree)
130 if b.Binding.Account != "" && b.Binding.Account != release.OpsAccount {
131 tree = filepath.Dir(tree)
132 }
133 if filepath.Base(tree) != "versions" || b.Path != release.VersionPath(filepath.Dir(tree), b.Binding.Account, b.Version, b.Binding.App) {
134 return os.ErrPermission
135 }
136 // The canonical version store must be hidden, not merely read-only.
137 if _, err := os.Stat(filepath.Join(path, "root", tree)); !os.IsNotExist(err) {
138 return os.ErrPermission
139 }
140 raw, err := generationRead(path + "/mountinfo")
141 if err != nil {
142 return err
143 }
144 boundMount := false
145 for _, line := range strings.Split(strings.TrimSpace(string(raw)), "\n") {
146 f := strings.Fields(line)
147 if len(f) < 10 {
148 return os.ErrPermission
149 }
150 source, e1 := generationUnescape(f[3])
151 point, e2 := generationUnescape(f[4])
152 if e1 != nil || e2 != nil || !filepath.IsAbs(source) || !filepath.IsAbs(point) {
153 return os.ErrPermission
154 }
155 separator := -1
156 for i := 6; i < len(f); i++ {
157 if f[i] == "-" {
158 separator = i
159 break
160 }
161 }
162 if separator < 6 || len(f) != separator+4 {
163 return os.ErrPermission
164 }
165 if f[separator+1] == "proc" {
166 if source == "/" {
167 visible, err := os.Readlink(filepath.Join(path, "root", point, "1/ns/pid"))
168 if err != nil || visible != privatePID {
169 return os.ErrPermission
170 }
171 } else if source != "/sys" && !strings.HasPrefix(source, "/sys/") {
172 return os.ErrPermission
173 }
174 }
175 major, minor, ok := strings.Cut(f[2], ":")
176 maj, e3 := strconv.ParseUint(major, 10, 32)
177 min, e4 := strconv.ParseUint(minor, 10, 32)
178 if !ok || e3 != nil || e4 != nil {
179 return os.ErrPermission
180 }
181 if unix.Mkdev(uint32(maj), uint32(min)) != b.Executable.Device {
182 continue
183 }
184 // The one reserved file is the only staged bind this namespace may hold.
185 if source == b.Path && point == "/"+bound {
186 boundMount = true
187 continue
188 }
189 below, e1 := filepath.Rel(tree, source)
190 above, e2 := filepath.Rel(source, tree)
191 if (e1 == nil && filepath.IsLocal(below)) || (e2 == nil && filepath.IsLocal(above) && point != "/") {
192 return os.ErrPermission
193 }
194 }
195 if !boundMount {
196 return os.ErrPermission
197 }
198 return nil
199}
200
201func (k generationKernel) replacedGroup(ctx context.Context, old release.GenerationReservation) error {
202 if !old.Closed || !isolatedGeneration(old) || old.Process == (release.GenerationProcess{}) {
203 return os.ErrPermission
204 }
205 boot, err := k.boot()
206 if err != nil {
207 return err
208 }
209 for _, next := range k.contained {
210 if next.Closed || next.Retirement != "" || !isolatedGeneration(next) || next.Process.Containment != 1 ||
211 next.Binding.Binding != old.Binding.Binding || next.Binding.Attempt == old.Binding.Attempt ||
212 next.Process.Invocation == "" || next.Process.Invocation == old.Process.Invocation ||
213 next.Process.Group != old.Process.Group || next.Binding.Target.BootID != boot {
214 continue
215 }
216 if old.Binding.Target.BootID == boot && next.Process.GroupIdentity == old.Process.GroupIdentity {
217 continue
218 }
219 path := filepath.Join(k.proc, strconv.Itoa(next.Process.PID))
220 status, err := generationRead(path + "/status")
221 if err != nil {
222 return err
223 }
224 k.contained = []release.GenerationReservation{next}
225 ok, err := k.containedTask(ctx, path, status)
226 if err != nil || !ok {
227 return os.ErrPermission
228 }
229 return k.emptyGroups(ctx, filepath.Join(k.cgroups, next.Process.Group))
230 }
231 return os.ErrPermission
232}
233