xref: /aosp_15_r20/external/libcap/cap/launch.go (revision 2810ac1b38eead2603277920c78344c84ddf3aff)
1package cap
2
3import (
4	"errors"
5	"os"
6	"runtime"
7	"sync"
8	"syscall"
9	"unsafe"
10)
11
12// Launcher holds a configuration for executing an optional callback
13// function and/or launching a child process with capability state
14// different from the parent.
15//
16// Note, go1.10 is the earliest version of the Go toolchain that can
17// support this abstraction.
18type Launcher struct {
19	mu sync.RWMutex
20
21	// Note, path and args must be set, or callbackFn. They cannot
22	// both be empty. In such cases .Launch() will error out.
23	path string
24	args []string
25	env  []string
26
27	callbackFn func(pa *syscall.ProcAttr, data interface{}) error
28
29	// The following are only honored when path is non empty.
30	changeUIDs bool
31	uid        int
32
33	changeGIDs bool
34	gid        int
35	groups     []int
36
37	changeMode bool
38	mode       Mode
39
40	iab *IAB
41
42	chroot string
43}
44
45// NewLauncher returns a new launcher for the specified program path
46// and args with the specified environment.
47func NewLauncher(path string, args []string, env []string) *Launcher {
48	return &Launcher{
49		path: path,
50		args: args,
51		env:  env,
52	}
53}
54
55// FuncLauncher returns a new launcher whose purpose is to only
56// execute fn in a disposable security context. This is a more bare
57// bones variant of the more elaborate program launcher returned by
58// cap.NewLauncher().
59//
60// Note, this launcher will fully ignore any overrides provided by the
61// (*Launcher).SetUID() etc. methods. Should your fn() code want to
62// run with a different capability state or other privilege, it should
63// use the cap.*() functions to set them directly. The cap package
64// will ensure that their effects are limited to the runtime of this
65// individual function invocation. Warning: executing non-cap.*()
66// syscall functions may corrupt the state of the program runtime and
67// lead to unpredictable results.
68//
69// The properties of fn are similar to those supplied via
70// (*Launcher).Callback(fn) method. However, this launcher is bare
71// bones because, when launching, all privilege management performed
72// by the fn() is fully discarded when the fn() completes
73// execution. That is, it does not end by exec()ing some program.
74func FuncLauncher(fn func(interface{}) error) *Launcher {
75	return &Launcher{
76		callbackFn: func(ignored *syscall.ProcAttr, data interface{}) error {
77			return fn(data)
78		},
79	}
80}
81
82// Callback changes the callback function for Launch() to call before
83// changing privilege. The only thing that is assumed is that the OS
84// thread in use to call this callback function at launch time will be
85// the one that ultimately calls fork to complete the launch of a path
86// specified executable. Any returned error value of said function
87// will terminate the launch process.
88//
89// A nil fn causes there to be no callback function invoked during a
90// Launch() sequence - it will remove any pre-existing callback.
91//
92// If the non-nil fn requires any effective capabilities in order to
93// run, they can be raised prior to calling .Launch() or inside the
94// callback function itself.
95//
96// If the specified callback fn should call any "cap" package
97// functions that change privilege state, these calls will only affect
98// the launch goroutine itself. While the launch is in progress, other
99// (non-launch) goroutines will block if they attempt to change
100// privilege state. These routines will unblock once there are no
101// in-flight launches.
102//
103// Note, the first argument provided to the callback function is the
104// *syscall.ProcAttr value to be used when a process launch is taking
105// place. A non-nil structure pointer can be modified by the callback
106// to enhance the launch. For example, the .Files field can be
107// overridden to affect how the launched process' stdin/out/err are
108// handled.
109//
110// Further, the 2nd argument to the callback function is provided at
111// Launch() invocation and can communicate contextual info to and from
112// the callback and the main process.
113func (attr *Launcher) Callback(fn func(*syscall.ProcAttr, interface{}) error) {
114	if attr == nil {
115		return
116	}
117	attr.mu.Lock()
118	defer attr.mu.Unlock()
119	attr.callbackFn = fn
120}
121
122// SetUID specifies the UID to be used by the launched command.
123func (attr *Launcher) SetUID(uid int) {
124	if attr == nil {
125		return
126	}
127	attr.mu.Lock()
128	defer attr.mu.Unlock()
129	attr.changeUIDs = true
130	attr.uid = uid
131}
132
133// SetGroups specifies the GID and supplementary groups for the
134// launched command.
135func (attr *Launcher) SetGroups(gid int, groups []int) {
136	if attr == nil {
137		return
138	}
139	attr.mu.Lock()
140	defer attr.mu.Unlock()
141	attr.changeGIDs = true
142	attr.gid = gid
143	attr.groups = groups
144}
145
146// SetMode specifies the libcap Mode to be used by the launched command.
147func (attr *Launcher) SetMode(mode Mode) {
148	if attr == nil {
149		return
150	}
151	attr.mu.Lock()
152	defer attr.mu.Unlock()
153	attr.changeMode = true
154	attr.mode = mode
155}
156
157// SetIAB specifies the IAB capability vectors to be inherited by the
158// launched command. A nil value means the prevailing vectors of the
159// parent will be inherited. Note, a duplicate of the provided IAB
160// tuple is actually stored, so concurrent modification of the iab
161// value does not affect the launcher.
162func (attr *Launcher) SetIAB(iab *IAB) {
163	if attr == nil {
164		return
165	}
166	attr.mu.Lock()
167	defer attr.mu.Unlock()
168	attr.iab, _ = iab.Dup()
169}
170
171// SetChroot specifies the chroot value to be used by the launched
172// command. An empty value means no-change from the prevailing value.
173func (attr *Launcher) SetChroot(root string) {
174	if attr == nil {
175		return
176	}
177	attr.mu.Lock()
178	defer attr.mu.Unlock()
179	attr.chroot = root
180}
181
182// lResult is used to get the result from the doomed launcher thread.
183type lResult struct {
184	// tgid holds the thread group id, which is an alias for the
185	// shared process id of the parent program.
186	tgid int
187
188	// tid holds the tid of the locked launching thread which dies
189	// as the launch completes.
190	tid int
191
192	// pid is the pid of the launched program (path, args). In
193	// the case of a FuncLaunch() this value is zero on success.
194	// pid holds -1 in the case of error.
195	pid int
196
197	// err is nil on success, but otherwise holds the reason the
198	// launch failed.
199	err error
200}
201
202// ErrLaunchFailed is returned if a launch was aborted with no more
203// specific error.
204var ErrLaunchFailed = errors.New("launch failed")
205
206// ErrNoLaunch indicates the go runtime available to this binary does
207// not reliably support launching. See cap.LaunchSupported.
208var ErrNoLaunch = errors.New("launch not supported")
209
210// ErrAmbiguousChroot indicates that the Launcher is being used in
211// addition to a callback supplied Chroot. The former should be used
212// exclusively for this.
213var ErrAmbiguousChroot = errors.New("use Launcher for chroot")
214
215// ErrAmbiguousIDs indicates that the Launcher is being used in
216// addition to a callback supplied Credentials. The former should be
217// used exclusively for this.
218var ErrAmbiguousIDs = errors.New("use Launcher for uids and gids")
219
220// ErrAmbiguousAmbient indicates that the Launcher is being used in
221// addition to a callback supplied ambient set and the former should
222// be used exclusively in a Launch call.
223var ErrAmbiguousAmbient = errors.New("use Launcher for ambient caps")
224
225// lName is the name we temporarily give to the launcher thread. Note,
226// this will likely stick around in the process tree if the Go runtime
227// is not cleaning up locked launcher OS threads.
228var lName = []byte("cap-launcher\000")
229
230// <uapi/linux/prctl.h>
231const prSetName = 15
232
233//go:uintptrescapes
234func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<- struct{}) {
235	if quit != nil {
236		defer close(quit)
237	}
238
239	// Thread group ID is the process ID.
240	tgid := syscall.Getpid()
241
242	// This code waits until we are not scheduled on the parent
243	// thread.  We will exit this thread once the child has
244	// launched.
245	runtime.LockOSThread()
246	tid := syscall.Gettid()
247	if tid == tgid {
248		// Force the go runtime to find a new thread to run
249		// on.  (It is really awkward to have a process'
250		// PID=TID thread in effectively a zombie state. The
251		// Go runtime has support for it, but pstree gives
252		// ugly output since the prSetName value sticks around
253		// after launch completion...
254		//
255		// (Optimize for time to debug by reducing ugly spam
256		// like this.)
257		quit := make(chan struct{})
258		go launch(result, attr, data, quit)
259
260		// Wait for that go routine to complete.
261		<-quit
262		runtime.UnlockOSThread()
263		return
264	}
265
266	// Provide a way to serialize the caller on the thread
267	// completing. This should be done by the one locked tid that
268	// does the ForkExec(). All the other threads have a different
269	// security context.
270	defer close(result)
271
272	// By never releasing the LockOSThread here, we guarantee that
273	// the runtime will terminate the current OS thread once this
274	// function returns.
275	scwSetState(launchIdle, launchActive, tid)
276
277	// Name the launcher thread - transient, but helps to debug if
278	// the callbackFn or something else hangs up.
279	singlesc.prctlrcall(prSetName, uintptr(unsafe.Pointer(&lName[0])), 0)
280
281	var pa *syscall.ProcAttr
282	var err error
283	var needChroot bool
284
285	// Only prepare a non-nil pa value if a path is provided.
286	if attr.path != "" {
287		// By default the following file descriptors are preserved for
288		// the child. The user should modify them in the callback for
289		// stdin/out/err redirection.
290		pa = &syscall.ProcAttr{
291			Files: []uintptr{0, 1, 2},
292		}
293		if len(attr.env) != 0 {
294			pa.Env = attr.env
295		} else {
296			pa.Env = os.Environ()
297		}
298	}
299
300	var pid int
301	if attr.callbackFn != nil {
302		if err = attr.callbackFn(pa, data); err != nil {
303			goto abort
304		}
305		if attr.path == "" {
306			goto abort
307		}
308	}
309
310	if needChroot, err = validatePA(pa, attr.chroot); err != nil {
311		goto abort
312	}
313	if attr.changeUIDs {
314		if err = singlesc.setUID(attr.uid); err != nil {
315			goto abort
316		}
317	}
318	if attr.changeGIDs {
319		if err = singlesc.setGroups(attr.gid, attr.groups); err != nil {
320			goto abort
321		}
322	}
323	if attr.changeMode {
324		if err = singlesc.setMode(attr.mode); err != nil {
325			goto abort
326		}
327	}
328	if attr.iab != nil {
329		// Note, since .iab is a private copy we don't need to
330		// lock it around this call.
331		if err = singlesc.iabSetProc(attr.iab); err != nil {
332			goto abort
333		}
334	}
335
336	if needChroot {
337		c := GetProc()
338		if err = c.SetFlag(Effective, true, SYS_CHROOT); err != nil {
339			goto abort
340		}
341		if err = singlesc.setProc(c); err != nil {
342			goto abort
343		}
344	}
345	pid, err = syscall.ForkExec(attr.path, attr.args, pa)
346
347abort:
348	if err != nil {
349		pid = -1
350	}
351	result <- lResult{
352		tgid: tgid,
353		tid:  tid,
354		pid:  pid,
355		err:  err,
356	}
357}
358
359// pollForThreadExit waits for a thread to terminate. Only after the
360// thread has safely exited is it safe to resume POSIX semantics
361// security state mirroring for the rest of the process threads.
362func (v lResult) pollForThreadExit() {
363	if v.tid == -1 {
364		return
365	}
366	for syscall.Tgkill(v.tgid, v.tid, 0) == nil {
367		runtime.Gosched()
368	}
369	scwSetState(launchActive, launchIdle, v.tid)
370}
371
372// Launch performs a callback function and/or new program launch with
373// a disposable security state. The data object, when not nil, can be
374// used to communicate with the callback. It can also be used to
375// return details from the callback function's execution.
376//
377// If the attr was created with NewLauncher(), this present function
378// will return the pid of the launched process, or -1 and a non-nil
379// error.
380//
381// If the attr was created with FuncLauncher(), this present function
382// will return 0, nil if the callback function exits without
383// error. Otherwise it will return -1 and the non-nil error of the
384// callback return value.
385//
386// Note, while the disposable security state thread makes some
387// operations seem more isolated - they are *not securely
388// isolated*. Launching is inherently violating the POSIX semantics
389// maintained by the rest of the "libcap/cap" package, so think of
390// launching as a convenience wrapper around fork()ing.
391//
392// Advanced user note: if the caller of this function thinks they know
393// what they are doing by using runtime.LockOSThread() before invoking
394// this function, they should understand that the OS thread invoking
395// (*Launcher).Launch() is *not* guaranteed to be the one used for the
396// disposable security state to perform the launch. If said caller
397// needs to run something on the disposable security state thread,
398// they should do it via the launch callback function mechanism. (The
399// Go runtime is complicated and this is why this Launch mechanism
400// provides the optional callback function.)
401func (attr *Launcher) Launch(data interface{}) (int, error) {
402	if !LaunchSupported {
403		return -1, ErrNoLaunch
404	}
405	if attr == nil {
406		return -1, ErrLaunchFailed
407	}
408	attr.mu.RLock()
409	defer attr.mu.RUnlock()
410	if attr.callbackFn == nil && (attr.path == "" || len(attr.args) == 0) {
411		return -1, ErrLaunchFailed
412	}
413
414	result := make(chan lResult)
415	go launch(result, attr, data, nil)
416	v, ok := <-result
417	if !ok {
418		return -1, ErrLaunchFailed
419	}
420	<-result // blocks until the launch() goroutine exits
421	v.pollForThreadExit()
422	return v.pid, v.err
423}
424