1package cap
2
3import (
4	"errors"
5	"os"
6	"runtime"
7	"syscall"
8	"unsafe"
9)
10
11// Launcher holds a configuration for launching a child process with
12// capability state different from (generally more restricted than)
13// the parent.
14//
15// Note, go1.10 is the earliest version of the Go toolchain that can
16// support this abstraction.
17type Launcher struct {
18	path string
19	args []string
20	env  []string
21
22	callbackFn func(pa *syscall.ProcAttr, data interface{}) error
23
24	changeUIDs bool
25	uid        int
26
27	changeGIDs bool
28	gid        int
29	groups     []int
30
31	changeMode bool
32	mode       Mode
33
34	iab *IAB
35
36	chroot string
37}
38
39// NewLauncher returns a new launcher for the specified program path
40// and args with the specified environment.
41func NewLauncher(path string, args []string, env []string) *Launcher {
42	return &Launcher{
43		path: path,
44		args: args,
45		env:  env,
46	}
47}
48
49// Callback specifies a callback for Launch() to call before changing
50// privilege. The only thing that is assumed is that the OS thread in
51// use to call this callback function at launch time will be the one
52// that ultimately calls fork. Any returned error value of said
53// function will terminate the launch process. A nil callback (the
54// default) is ignored. The specified callback fn should not call any
55// "cap" package functions since this may deadlock or generate
56// undefined behavior for the parent process.
57func (attr *Launcher) Callback(fn func(*syscall.ProcAttr, interface{}) error) {
58	attr.callbackFn = fn
59}
60
61// SetUID specifies the UID to be used by the launched command.
62func (attr *Launcher) SetUID(uid int) {
63	attr.changeUIDs = true
64	attr.uid = uid
65}
66
67// SetGroups specifies the GID and supplementary groups for the
68// launched command.
69func (attr *Launcher) SetGroups(gid int, groups []int) {
70	attr.changeGIDs = true
71	attr.gid = gid
72	attr.groups = groups
73}
74
75// SetMode specifies the libcap Mode to be used by the launched command.
76func (attr *Launcher) SetMode(mode Mode) {
77	attr.changeMode = true
78	attr.mode = mode
79}
80
81// SetIAB specifies the AIB capability vectors to be inherited by the
82// launched command. A nil value means the prevailing vectors of the
83// parent will be inherited.
84func (attr *Launcher) SetIAB(iab *IAB) {
85	attr.iab = iab
86}
87
88// SetChroot specifies the chroot value to be used by the launched
89// command. An empty value means no-change from the prevailing value.
90func (attr *Launcher) SetChroot(root string) {
91	attr.chroot = root
92}
93
94// lResult is used to get the result from the doomed launcher thread.
95type lResult struct {
96	pid int
97	err error
98}
99
100// ErrLaunchFailed is returned if a launch was aborted with no more
101// specific error.
102var ErrLaunchFailed = errors.New("launch failed")
103
104// ErrNoLaunch indicates the go runtime available to this binary does
105// not reliably support launching. See cap.LaunchSupported.
106var ErrNoLaunch = errors.New("launch not supported")
107
108// ErrAmbiguousChroot indicates that the Launcher is being used in
109// addition to a callback supplied Chroot. The former should be used
110// exclusively for this.
111var ErrAmbiguousChroot = errors.New("use Launcher for chroot")
112
113// ErrAmbiguousIDs indicates that the Launcher is being used in
114// addition to a callback supplied Credentials. The former should be
115// used exclusively for this.
116var ErrAmbiguousIDs = errors.New("use Launcher for uids and gids")
117
118// ErrAmbiguousAmbient indicates that the Launcher is being used in
119// addition to a callback supplied ambient set and the former should
120// be used exclusively in a Launch call.
121var ErrAmbiguousAmbient = errors.New("use Launcher for ambient caps")
122
123// lName is the name we temporarily give to the launcher thread. Note,
124// this will likely stick around in the process tree if the Go runtime
125// is not cleaning up locked launcher OS threads.
126var lName = []byte("cap-launcher\000")
127
128// <uapi/linux/prctl.h>
129const prSetName = 15
130
131//go:uintptrescapes
132func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<- struct{}) {
133	if quit != nil {
134		defer close(quit)
135	}
136
137	pid := syscall.Getpid()
138	// Wait until we are not scheduled on the parent thread.  We
139	// will exit this thread once the child has launched, and
140	// don't want other goroutines to use this thread afterwards.
141	runtime.LockOSThread()
142	tid := syscall.Gettid()
143	if tid == pid {
144		// Force the go runtime to find a new thread to run on.
145		quit := make(chan struct{})
146		go launch(result, attr, data, quit)
147
148		// Wait for that go routine to complete.
149		<-quit
150		runtime.UnlockOSThread()
151		return
152	}
153
154	// By never releasing the LockOSThread here, we guarantee that
155	// the runtime will terminate the current OS thread once this
156	// function returns.
157
158	// Name the launcher thread - transient, but helps to debug if
159	// the callbackFn or something else hangs up.
160	singlesc.prctlrcall(prSetName, uintptr(unsafe.Pointer(&lName[0])), 0)
161
162	// Provide a way to serialize the caller on the thread
163	// completing.
164	defer close(result)
165
166	pa := &syscall.ProcAttr{
167		Files: []uintptr{0, 1, 2},
168	}
169	var err error
170	var needChroot bool
171
172	if len(attr.env) != 0 {
173		pa.Env = attr.env
174	} else {
175		pa.Env = os.Environ()
176	}
177
178	if attr.callbackFn != nil {
179		if err = attr.callbackFn(pa, data); err != nil {
180			goto abort
181		}
182	}
183
184	if needChroot, err = validatePA(pa, attr.chroot); err != nil {
185		goto abort
186	}
187	if attr.changeUIDs {
188		if err = singlesc.setUID(attr.uid); err != nil {
189			goto abort
190		}
191	}
192	if attr.changeGIDs {
193		if err = singlesc.setGroups(attr.gid, attr.groups); err != nil {
194			goto abort
195		}
196	}
197	if attr.changeMode {
198		if err = singlesc.setMode(attr.mode); err != nil {
199			goto abort
200		}
201	}
202	if attr.iab != nil {
203		if err = singlesc.iabSetProc(attr.iab); err != nil {
204			goto abort
205		}
206	}
207
208	if needChroot {
209		c := GetProc()
210		if err = c.SetFlag(Effective, true, SYS_CHROOT); err != nil {
211			goto abort
212		}
213		if err = singlesc.setProc(c); err != nil {
214			goto abort
215		}
216	}
217	pid, err = syscall.ForkExec(attr.path, attr.args, pa)
218
219abort:
220	if err != nil {
221		pid = -1
222	}
223	result <- lResult{pid: pid, err: err}
224}
225
226// Launch performs a new program launch with security state specified
227// in the supplied attr settings.
228func (attr *Launcher) Launch(data interface{}) (int, error) {
229	if attr.path == "" || len(attr.args) == 0 {
230		return -1, ErrLaunchFailed
231	}
232	if !LaunchSupported {
233		return -1, ErrNoLaunch
234	}
235
236	scwMu.Lock()
237	defer scwMu.Unlock()
238	result := make(chan lResult)
239
240	go launch(result, attr, data, nil)
241	for {
242		select {
243		case v, ok := <-result:
244			if !ok {
245				return -1, ErrLaunchFailed
246			}
247			return v.pid, v.err
248		default:
249			runtime.Gosched()
250		}
251	}
252}
253