1 package main
2
3 import (
4 "errors"
5 "fmt"
6 "net"
7 "os"
8 "os/exec"
9 "path/filepath"
10 "strconv"
11
12 "github.com/coreos/go-systemd/v22/activation"
13 "github.com/opencontainers/runtime-spec/specs-go"
14 selinux "github.com/opencontainers/selinux/go-selinux"
15 "github.com/sirupsen/logrus"
16 "github.com/urfave/cli"
17 "golang.org/x/sys/unix"
18
19 "github.com/opencontainers/runc/libcontainer"
20 "github.com/opencontainers/runc/libcontainer/configs"
21 "github.com/opencontainers/runc/libcontainer/specconv"
22 "github.com/opencontainers/runc/libcontainer/utils"
23 )
24
25 var errEmptyID = errors.New("container id cannot be empty")
26
27
28 func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
29 root := context.GlobalString("root")
30 abs, err := filepath.Abs(root)
31 if err != nil {
32 return nil, err
33 }
34
35
36
37
38 newuidmap, err := exec.LookPath("newuidmap")
39 if err != nil {
40 newuidmap = ""
41 }
42 newgidmap, err := exec.LookPath("newgidmap")
43 if err != nil {
44 newgidmap = ""
45 }
46
47 return libcontainer.New(abs,
48 libcontainer.CriuPath(context.GlobalString("criu")),
49 libcontainer.NewuidmapPath(newuidmap),
50 libcontainer.NewgidmapPath(newgidmap))
51 }
52
53
54
55 func getContainer(context *cli.Context) (libcontainer.Container, error) {
56 id := context.Args().First()
57 if id == "" {
58 return nil, errEmptyID
59 }
60 factory, err := loadFactory(context)
61 if err != nil {
62 return nil, err
63 }
64 return factory.Load(id)
65 }
66
67 func getDefaultImagePath() string {
68 cwd, err := os.Getwd()
69 if err != nil {
70 panic(err)
71 }
72 return filepath.Join(cwd, "checkpoint")
73 }
74
75
76
77 func newProcess(p specs.Process) (*libcontainer.Process, error) {
78 lp := &libcontainer.Process{
79 Args: p.Args,
80 Env: p.Env,
81
82 User: fmt.Sprintf("%d:%d", p.User.UID, p.User.GID),
83 Cwd: p.Cwd,
84 Label: p.SelinuxLabel,
85 NoNewPrivileges: &p.NoNewPrivileges,
86 AppArmorProfile: p.ApparmorProfile,
87 }
88
89 if p.ConsoleSize != nil {
90 lp.ConsoleWidth = uint16(p.ConsoleSize.Width)
91 lp.ConsoleHeight = uint16(p.ConsoleSize.Height)
92 }
93
94 if p.Capabilities != nil {
95 lp.Capabilities = &configs.Capabilities{}
96 lp.Capabilities.Bounding = p.Capabilities.Bounding
97 lp.Capabilities.Effective = p.Capabilities.Effective
98 lp.Capabilities.Inheritable = p.Capabilities.Inheritable
99 lp.Capabilities.Permitted = p.Capabilities.Permitted
100 lp.Capabilities.Ambient = p.Capabilities.Ambient
101 }
102 for _, gid := range p.User.AdditionalGids {
103 lp.AdditionalGroups = append(lp.AdditionalGroups, strconv.FormatUint(uint64(gid), 10))
104 }
105 for _, rlimit := range p.Rlimits {
106 rl, err := createLibContainerRlimit(rlimit)
107 if err != nil {
108 return nil, err
109 }
110 lp.Rlimits = append(lp.Rlimits, rl)
111 }
112 return lp, nil
113 }
114
115 func destroy(container libcontainer.Container) {
116 if err := container.Destroy(); err != nil {
117 logrus.Error(err)
118 }
119 }
120
121
122 func setupIO(process *libcontainer.Process, rootuid, rootgid int, createTTY, detach bool, sockpath string) (*tty, error) {
123 if createTTY {
124 process.Stdin = nil
125 process.Stdout = nil
126 process.Stderr = nil
127 t := &tty{}
128 if !detach {
129 if err := t.initHostConsole(); err != nil {
130 return nil, err
131 }
132 parent, child, err := utils.NewSockPair("console")
133 if err != nil {
134 return nil, err
135 }
136 process.ConsoleSocket = child
137 t.postStart = append(t.postStart, parent, child)
138 t.consoleC = make(chan error, 1)
139 go func() {
140 t.consoleC <- t.recvtty(parent)
141 }()
142 } else {
143
144 conn, err := net.Dial("unix", sockpath)
145 if err != nil {
146 return nil, err
147 }
148 uc, ok := conn.(*net.UnixConn)
149 if !ok {
150 return nil, errors.New("casting to UnixConn failed")
151 }
152 t.postStart = append(t.postStart, uc)
153 socket, err := uc.File()
154 if err != nil {
155 return nil, err
156 }
157 t.postStart = append(t.postStart, socket)
158 process.ConsoleSocket = socket
159 }
160 return t, nil
161 }
162
163
164 if detach {
165 inheritStdio(process)
166 return &tty{}, nil
167 }
168 return setupProcessPipes(process, rootuid, rootgid)
169 }
170
171
172
173
174 func createPidFile(path string, process *libcontainer.Process) error {
175 pid, err := process.Pid()
176 if err != nil {
177 return err
178 }
179 var (
180 tmpDir = filepath.Dir(path)
181 tmpName = filepath.Join(tmpDir, "."+filepath.Base(path))
182 )
183 f, err := os.OpenFile(tmpName, os.O_RDWR|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0o666)
184 if err != nil {
185 return err
186 }
187 _, err = f.WriteString(strconv.Itoa(pid))
188 f.Close()
189 if err != nil {
190 return err
191 }
192 return os.Rename(tmpName, path)
193 }
194
195 func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error) {
196 rootlessCg, err := shouldUseRootlessCgroupManager(context)
197 if err != nil {
198 return nil, err
199 }
200 config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{
201 CgroupName: id,
202 UseSystemdCgroup: context.GlobalBool("systemd-cgroup"),
203 NoPivotRoot: context.Bool("no-pivot"),
204 NoNewKeyring: context.Bool("no-new-keyring"),
205 Spec: spec,
206 RootlessEUID: os.Geteuid() != 0,
207 RootlessCgroups: rootlessCg,
208 })
209 if err != nil {
210 return nil, err
211 }
212
213 factory, err := loadFactory(context)
214 if err != nil {
215 return nil, err
216 }
217 return factory.Create(id, config)
218 }
219
220 type runner struct {
221 init bool
222 enableSubreaper bool
223 shouldDestroy bool
224 detach bool
225 listenFDs []*os.File
226 preserveFDs int
227 pidFile string
228 consoleSocket string
229 container libcontainer.Container
230 action CtAct
231 notifySocket *notifySocket
232 criuOpts *libcontainer.CriuOpts
233 subCgroupPaths map[string]string
234 }
235
236 func (r *runner) run(config *specs.Process) (int, error) {
237 var err error
238 defer func() {
239 if err != nil {
240 r.destroy()
241 }
242 }()
243 if err = r.checkTerminal(config); err != nil {
244 return -1, err
245 }
246 process, err := newProcess(*config)
247 if err != nil {
248 return -1, err
249 }
250 process.LogLevel = strconv.Itoa(int(logrus.GetLevel()))
251
252 process.Init = r.init
253 process.SubCgroupPaths = r.subCgroupPaths
254 if len(r.listenFDs) > 0 {
255 process.Env = append(process.Env, "LISTEN_FDS="+strconv.Itoa(len(r.listenFDs)), "LISTEN_PID=1")
256 process.ExtraFiles = append(process.ExtraFiles, r.listenFDs...)
257 }
258 baseFd := 3 + len(process.ExtraFiles)
259 for i := baseFd; i < baseFd+r.preserveFDs; i++ {
260 _, err = os.Stat("/proc/self/fd/" + strconv.Itoa(i))
261 if err != nil {
262 return -1, fmt.Errorf("unable to stat preserved-fd %d (of %d): %w", i-baseFd, r.preserveFDs, err)
263 }
264 process.ExtraFiles = append(process.ExtraFiles, os.NewFile(uintptr(i), "PreserveFD:"+strconv.Itoa(i)))
265 }
266 rootuid, err := r.container.Config().HostRootUID()
267 if err != nil {
268 return -1, err
269 }
270 rootgid, err := r.container.Config().HostRootGID()
271 if err != nil {
272 return -1, err
273 }
274 detach := r.detach || (r.action == CT_ACT_CREATE)
275
276
277
278 handler := newSignalHandler(r.enableSubreaper, r.notifySocket)
279 tty, err := setupIO(process, rootuid, rootgid, config.Terminal, detach, r.consoleSocket)
280 if err != nil {
281 return -1, err
282 }
283 defer tty.Close()
284
285 switch r.action {
286 case CT_ACT_CREATE:
287 err = r.container.Start(process)
288 case CT_ACT_RESTORE:
289 err = r.container.Restore(process, r.criuOpts)
290 case CT_ACT_RUN:
291 err = r.container.Run(process)
292 default:
293 panic("Unknown action")
294 }
295 if err != nil {
296 return -1, err
297 }
298 if err = tty.waitConsole(); err != nil {
299 r.terminate(process)
300 return -1, err
301 }
302 tty.ClosePostStart()
303 if r.pidFile != "" {
304 if err = createPidFile(r.pidFile, process); err != nil {
305 r.terminate(process)
306 return -1, err
307 }
308 }
309 status, err := handler.forward(process, tty, detach)
310 if err != nil {
311 r.terminate(process)
312 }
313 if detach {
314 return 0, nil
315 }
316 if err == nil {
317 r.destroy()
318 }
319 return status, err
320 }
321
322 func (r *runner) destroy() {
323 if r.shouldDestroy {
324 destroy(r.container)
325 }
326 }
327
328 func (r *runner) terminate(p *libcontainer.Process) {
329 _ = p.Signal(unix.SIGKILL)
330 _, _ = p.Wait()
331 }
332
333 func (r *runner) checkTerminal(config *specs.Process) error {
334 detach := r.detach || (r.action == CT_ACT_CREATE)
335
336 if detach && config.Terminal && r.consoleSocket == "" {
337 return errors.New("cannot allocate tty if runc will detach without setting console socket")
338 }
339 if (!detach || !config.Terminal) && r.consoleSocket != "" {
340 return errors.New("cannot use console socket if runc will not detach or allocate tty")
341 }
342 return nil
343 }
344
345 func validateProcessSpec(spec *specs.Process) error {
346 if spec == nil {
347 return errors.New("process property must not be empty")
348 }
349 if spec.Cwd == "" {
350 return errors.New("Cwd property must not be empty")
351 }
352 if !filepath.IsAbs(spec.Cwd) {
353 return errors.New("Cwd must be an absolute path")
354 }
355 if len(spec.Args) == 0 {
356 return errors.New("args must not be empty")
357 }
358 if spec.SelinuxLabel != "" && !selinux.GetEnabled() {
359 return errors.New("selinux label is specified in config, but selinux is disabled or not supported")
360 }
361 return nil
362 }
363
364 type CtAct uint8
365
366 const (
367 CT_ACT_CREATE CtAct = iota + 1
368 CT_ACT_RUN
369 CT_ACT_RESTORE
370 )
371
372 func startContainer(context *cli.Context, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) {
373 if err := revisePidFile(context); err != nil {
374 return -1, err
375 }
376 spec, err := setupSpec(context)
377 if err != nil {
378 return -1, err
379 }
380
381 id := context.Args().First()
382 if id == "" {
383 return -1, errEmptyID
384 }
385
386 notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
387 if notifySocket != nil {
388 notifySocket.setupSpec(spec)
389 }
390
391 container, err := createContainer(context, id, spec)
392 if err != nil {
393 return -1, err
394 }
395
396 if notifySocket != nil {
397 if err := notifySocket.setupSocketDirectory(); err != nil {
398 return -1, err
399 }
400 if action == CT_ACT_RUN {
401 if err := notifySocket.bindSocket(); err != nil {
402 return -1, err
403 }
404 }
405 }
406
407
408 listenFDs := []*os.File{}
409 if os.Getenv("LISTEN_FDS") != "" {
410 listenFDs = activation.Files(false)
411 }
412
413 r := &runner{
414 enableSubreaper: !context.Bool("no-subreaper"),
415 shouldDestroy: !context.Bool("keep"),
416 container: container,
417 listenFDs: listenFDs,
418 notifySocket: notifySocket,
419 consoleSocket: context.String("console-socket"),
420 detach: context.Bool("detach"),
421 pidFile: context.String("pid-file"),
422 preserveFDs: context.Int("preserve-fds"),
423 action: action,
424 criuOpts: criuOpts,
425 init: true,
426 }
427 return r.run(spec.Process)
428 }
429
View as plain text