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