aboutsummaryrefslogtreecommitdiff
path: root/cap/launch.go
blob: 6145f3e1d7c7372b5ab87b309c3c4e77165bd181 (plain)
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
package cap

import (
	"errors"
	"os"
	"runtime"
	"syscall"
	"unsafe"
)

// Launcher holds a configuration for executing an optional callback
// function and/or launching a child process with capability state
// different from the parent.
//
// Note, go1.10 is the earliest version of the Go toolchain that can
// support this abstraction.
type Launcher struct {
	// Note, path and args must be set, or callbackFn. They cannot
	// both be empty. In such cases .Launch() will error out.
	path string
	args []string
	env  []string

	callbackFn func(pa *syscall.ProcAttr, data interface{}) error

	// The following are only honored when path is non empty.
	changeUIDs bool
	uid        int

	changeGIDs bool
	gid        int
	groups     []int

	changeMode bool
	mode       Mode

	iab *IAB

	chroot string
}

// NewLauncher returns a new launcher for the specified program path
// and args with the specified environment.
func NewLauncher(path string, args []string, env []string) *Launcher {
	return &Launcher{
		path: path,
		args: args,
		env:  env,
	}
}

// FuncLauncher returns a new launcher whose purpose is to only
// execute fn in a disposable security context. This is a more bare
// bones variant of the more elaborate program launcher returned by
// cap.NewLauncher().
//
// Note, this launcher will fully ignore any overrides provided by the
// (*Launcher).SetUID() etc. methods. Should your fn() code want to
// run with a different capability state or other privilege, it should
// use the cap.*() functions to set them directly. The cap package
// will ensure that their effects are limited to the runtime of this
// individual function invocation. Warning: executing non-cap.*()
// syscall functions may corrupt the state of the program runtime and
// lead to unpredictable results.
//
// The properties of fn are similar to those supplied via
// (*Launcher).Callback(fn) method. However, this launcher is bare
// bones because, when launching, all privilege management performed
// by the fn() is fully discarded when the fn() completes
// execution. That is, it does not end by exec()ing some program.
func FuncLauncher(fn func(interface{}) error) *Launcher {
	return &Launcher{
		callbackFn: func(ignored *syscall.ProcAttr, data interface{}) error {
			return fn(data)
		},
	}
}

// Callback changes the callback function for Launch() to call before
// changing privilege. The only thing that is assumed is that the OS
// thread in use to call this callback function at launch time will be
// the one that ultimately calls fork to complete the launch of a path
// specified executable. Any returned error value of said function
// will terminate the launch process.
//
// A nil fn causes there to be no callback function invoked during a
// Launch() sequence - it will remove any pre-existing callback.
//
// If the non-nil fn requires any effective capabilities in order to
// run, they can be raised prior to calling .Launch() or inside the
// callback function itself.
//
// If the specified callback fn should call any "cap" package
// functions that change privilege state, these calls will only affect
// the launch goroutine itself. While the launch is in progress, other
// (non-launch) goroutines will block if they attempt to change
// privilege state. These routines will unblock once there are no
// in-flight launches.
//
// Note, the first argument provided to the callback function is the
// *syscall.ProcAttr value to be used when a process launch is taking
// place. A non-nil structure pointer can be modified by the callback
// to enhance the launch. For example, the .Files field can be
// overridden to affect how the launched process' stdin/out/err are
// handled.
//
// Further, the 2nd argument to the callback function is provided at
// Launch() invocation and can communicate contextual info to and from
// the callback and the main process.
func (attr *Launcher) Callback(fn func(*syscall.ProcAttr, interface{}) error) {
	attr.callbackFn = fn
}

// SetUID specifies the UID to be used by the launched command.
func (attr *Launcher) SetUID(uid int) {
	attr.changeUIDs = true
	attr.uid = uid
}

// SetGroups specifies the GID and supplementary groups for the
// launched command.
func (attr *Launcher) SetGroups(gid int, groups []int) {
	attr.changeGIDs = true
	attr.gid = gid
	attr.groups = groups
}

// SetMode specifies the libcap Mode to be used by the launched command.
func (attr *Launcher) SetMode(mode Mode) {
	attr.changeMode = true
	attr.mode = mode
}

// SetIAB specifies the AIB capability vectors to be inherited by the
// launched command. A nil value means the prevailing vectors of the
// parent will be inherited.
func (attr *Launcher) SetIAB(iab *IAB) {
	attr.iab = iab
}

// SetChroot specifies the chroot value to be used by the launched
// command. An empty value means no-change from the prevailing value.
func (attr *Launcher) SetChroot(root string) {
	attr.chroot = root
}

// lResult is used to get the result from the doomed launcher thread.
type lResult struct {
	// tid holds the tid of the locked launching thread which dies
	// as the launch completes.
	tid int

	// pid is the pid of the launched program (path, args). In
	// the case of a FuncLaunch() this value is zero on success.
	// pid holds -1 in the case of error.
	pid int

	// err is nil on success, but otherwise holds the reason the
	// launch failed.
	err error
}

// ErrLaunchFailed is returned if a launch was aborted with no more
// specific error.
var ErrLaunchFailed = errors.New("launch failed")

// ErrNoLaunch indicates the go runtime available to this binary does
// not reliably support launching. See cap.LaunchSupported.
var ErrNoLaunch = errors.New("launch not supported")

// ErrAmbiguousChroot indicates that the Launcher is being used in
// addition to a callback supplied Chroot. The former should be used
// exclusively for this.
var ErrAmbiguousChroot = errors.New("use Launcher for chroot")

// ErrAmbiguousIDs indicates that the Launcher is being used in
// addition to a callback supplied Credentials. The former should be
// used exclusively for this.
var ErrAmbiguousIDs = errors.New("use Launcher for uids and gids")

// ErrAmbiguousAmbient indicates that the Launcher is being used in
// addition to a callback supplied ambient set and the former should
// be used exclusively in a Launch call.
var ErrAmbiguousAmbient = errors.New("use Launcher for ambient caps")

// lName is the name we temporarily give to the launcher thread. Note,
// this will likely stick around in the process tree if the Go runtime
// is not cleaning up locked launcher OS threads.
var lName = []byte("cap-launcher\000")

// <uapi/linux/prctl.h>
const prSetName = 15

//go:uintptrescapes
func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<- struct{}) {
	if quit != nil {
		defer close(quit)
	}

	pid := syscall.Getpid()
	// This code waits until we are not scheduled on the parent
	// thread.  We will exit this thread once the child has
	// launched.
	runtime.LockOSThread()
	tid := syscall.Gettid()
	if tid == pid {
		// Force the go runtime to find a new thread to run
		// on.  (It is really awkward to have a process'
		// PID=TID thread in effectively a zombie state. The
		// Go runtime has support for it, but pstree gives
		// ugly output since the prSetName value sticks around
		// after launch completion...
		//
		// (Optimize for time to debug by reducing ugly spam
		// like this.)
		quit := make(chan struct{})
		go launch(result, attr, data, quit)

		// Wait for that go routine to complete.
		<-quit
		runtime.UnlockOSThread()
		return
	}

	// By never releasing the LockOSThread here, we guarantee that
	// the runtime will terminate the current OS thread once this
	// function returns.
	scwSetState(launchIdle, launchActive, tid)

	// Name the launcher thread - transient, but helps to debug if
	// the callbackFn or something else hangs up.
	singlesc.prctlrcall(prSetName, uintptr(unsafe.Pointer(&lName[0])), 0)

	// Provide a way to serialize the caller on the thread
	// completing.
	defer close(result)

	var pa *syscall.ProcAttr
	var err error
	var needChroot bool

	// Only prepare a non-nil pa value if a path is provided.
	if attr.path != "" {
		// By default the following file descriptors are preserved for
		// the child. The user should modify them in the callback for
		// stdin/out/err redirection.
		pa = &syscall.ProcAttr{
			Files: []uintptr{0, 1, 2},
		}
		if len(attr.env) != 0 {
			pa.Env = attr.env
		} else {
			pa.Env = os.Environ()
		}
	}

	if attr.callbackFn != nil {
		if err = attr.callbackFn(pa, data); err != nil {
			goto abort
		}
		if attr.path == "" {
			pid = 0
			goto abort
		}
	}

	if needChroot, err = validatePA(pa, attr.chroot); err != nil {
		goto abort
	}
	if attr.changeUIDs {
		if err = singlesc.setUID(attr.uid); err != nil {
			goto abort
		}
	}
	if attr.changeGIDs {
		if err = singlesc.setGroups(attr.gid, attr.groups); err != nil {
			goto abort
		}
	}
	if attr.changeMode {
		if err = singlesc.setMode(attr.mode); err != nil {
			goto abort
		}
	}
	if attr.iab != nil {
		if err = singlesc.iabSetProc(attr.iab); err != nil {
			goto abort
		}
	}

	if needChroot {
		c := GetProc()
		if err = c.SetFlag(Effective, true, SYS_CHROOT); err != nil {
			goto abort
		}
		if err = singlesc.setProc(c); err != nil {
			goto abort
		}
	}
	pid, err = syscall.ForkExec(attr.path, attr.args, pa)

abort:
	if err != nil {
		pid = -1
	}
	result <- lResult{
		tid: tid,
		pid: pid,
		err: err,
	}
}

// Launch performs a callback function and/or new program launch with
// a disposable security state. The data object, when not nil, can be
// used to communicate with the callback. It can also be used to
// return details from the callback functions execution.
//
// If the attr was created with NewLauncher(), this present function
// will return the pid of the launched process, or -1 and a non-nil
// error.
//
// If the attr was created with FuncLauncher(), this present function
// will return 0, nil if the callback function exits without
// error. Otherwise it will return -1 and the non-nil error of the
// callback return value.
//
// Note, while the disposable security state thread makes some
// oprerations seem more isolated - they are *not securely
// isolated*. Launching is inherently violating the POSIX semantics
// maintained by the rest of the "libcap/cap" package, so think of
// launching as a convenience wrapper around fork()ing.
//
// Advanced user note: if the caller of this function thinks they know
// what they are doing by using runtime.LockOSThread() before invoking
// this function, they should understand that the OS Thread invoking
// (*Launcher).Launch() is *not guaranteed* to be the one used for the
// disposable security state to perform the launch. If said caller
// needs to run something on the disposable security state thread,
// they should do it via the launch callback function mechanism. (The
// Go runtime is complicated and this is why this Launch mechanism
// provides the optional callback function.)
func (attr *Launcher) Launch(data interface{}) (int, error) {
	if !LaunchSupported {
		return -1, ErrNoLaunch
	}
	if attr.callbackFn == nil && (attr.path == "" || len(attr.args) == 0) {
		return -1, ErrLaunchFailed
	}

	result := make(chan lResult)
	go launch(result, attr, data, nil)
	for {
		select {
		case v, ok := <-result:
			if !ok {
				return -1, ErrLaunchFailed
			}
			if v.tid != -1 {
				defer scwSetState(launchActive, launchIdle, v.tid)
			}
			return v.pid, v.err
		default:
			runtime.Gosched()
		}
	}
}