File: rootless.go

package info (click to toggle)
singularity-container 4.0.3%2Bds1-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 21,672 kB
  • sloc: asm: 3,857; sh: 2,125; ansic: 1,677; awk: 414; makefile: 110; python: 99
file content (164 lines) | stat: -rw-r--r-- 4,262 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// Copyright (c) 2023, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.

package rootless

import (
	"os"
	"os/exec"
	"os/signal"
	"os/user"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"

	"github.com/sylabs/singularity/v4/internal/pkg/buildcfg"
	fakerootConfig "github.com/sylabs/singularity/v4/internal/pkg/runtime/engine/fakeroot/config"
	"github.com/sylabs/singularity/v4/internal/pkg/util/starter"
	"github.com/sylabs/singularity/v4/pkg/runtime/engine/config"
	"github.com/sylabs/singularity/v4/pkg/sylog"
)

const (
	NSEnv  = "_SINGULARITY_NAMESPACE"
	UIDEnv = "_CONTAINERS_ROOTLESS_UID"
	GIDEnv = "_CONTAINERS_ROOTLESS_GID"
)

// Getuid retrieves the uid stored in the env var _CONTAINERS_ROOTLESS_UID, or
// the current euid if the env var is not set.
func Getuid() (uid int, err error) {
	u := os.Getenv(UIDEnv)
	if u != "" {
		return strconv.Atoi(u)
	}
	return os.Geteuid(), nil
}

// Getgid retrieves the uid stored in the env var _CONTAINERS_ROOTLESS_GID, or
// the current egid if the env var is not set.
func Getgid() (uid int, err error) {
	g := os.Getenv(GIDEnv)
	if g != "" {
		return strconv.Atoi(g)
	}
	return os.Getegid(), nil
}

// GetUser retrieves the User struct for the uid stored in the env var
// _CONTAINERS_ROOTLESS_UID, or the current euid if the env var is not set.
func GetUser() (*user.User, error) {
	u := os.Getenv(UIDEnv)
	if u != "" {
		return user.LookupId(u)
	}
	return user.Current()
}

// InNS returns true if we are in a namespace created using this package.
func InNS() bool {
	_, envSet := os.LookupEnv(NSEnv)
	return envSet
}

// ExecWithFakeroot will exec singularity with provided args, in a
// subuid/gid-mapped fakeroot user namespace. This uses the fakeroot engine.
func ExecWithFakeroot(args []string) error {
	singularityBin := []string{
		filepath.Join(buildcfg.BINDIR, "singularity"),
	}
	args = append(singularityBin, args...)

	env := os.Environ()
	env = append(env, NSEnv+"=TRUE")
	// Use _CONTAINERS_ROOTLESS_xID naming for these vars as they are required
	// by our use of containers/image for OCI image handling.
	env = append(env, UIDEnv+"="+strconv.Itoa(os.Geteuid()))
	env = append(env, GIDEnv+"="+strconv.Itoa(os.Getegid()))

	sylog.Debugf("Calling fakeroot engine to execute %q", strings.Join(args, " "))

	cfg := &config.Common{
		EngineName:  fakerootConfig.Name,
		ContainerID: "fakeroot",
		EngineConfig: &fakerootConfig.EngineConfig{
			Envs:        env,
			Args:        args,
			NoPIDNS:     true,
			NoSetgroups: true,
		},
	}

	return starter.Exec(
		"Singularity oci fakeroot",
		cfg,
	)
}

// RunInMountNS will run singularity with provided args, in a mount
// namespace only.
func RunInMountNS(args []string) error {
	singularityBin := filepath.Join(buildcfg.BINDIR, "singularity")

	env := os.Environ()
	env = append(env, NSEnv+"=TRUE")

	cmd := exec.Command(singularityBin, args...)
	cmd.Env = env
	cmd.SysProcAttr = &syscall.SysProcAttr{}
	// Unshare mount namespace
	cmd.SysProcAttr.Unshareflags = syscall.CLONE_NEWNS
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	signals := make(chan os.Signal, 2)
	signal.Notify(signals)
	errChan := make(chan error, 1)

	err := cmd.Start()
	if err != nil {
		return err
	}

	go func() {
		errChan <- cmd.Wait()
	}()

	for {
		select {
		case s := <-signals:
			sylog.Debugf("Received signal %s", s.String())
			switch s {
			case syscall.SIGCHLD:
				break
			case syscall.SIGURG:
				// Ignore SIGURG, which is used for non-cooperative goroutine
				// preemption starting with Go 1.14. For more information, see
				// https://github.com/golang/go/issues/24543.
				break
			default:
				//nolint:forcetypeassert
				signal := s.(syscall.Signal)
				if err := syscall.Kill(cmd.Process.Pid, signal); err != nil {
					return err
				}
			}
		case err := <-errChan:
			if e, ok := err.(*exec.ExitError); ok {
				status, ok := e.Sys().(syscall.WaitStatus)
				if ok && status.Signaled() {
					os.Exit(128 + int(status.Signal()))
				}
				os.Exit(e.ExitCode())
			}
			if err == nil {
				os.Exit(0)
			}
			return err
		}
	}
}