aboutsummaryrefslogtreecommitdiff
path: root/cap/launch.go
diff options
context:
space:
mode:
Diffstat (limited to 'cap/launch.go')
-rw-r--r--cap/launch.go117
1 files changed, 87 insertions, 30 deletions
diff --git a/cap/launch.go b/cap/launch.go
index 6145f3e..de7fd90 100644
--- a/cap/launch.go
+++ b/cap/launch.go
@@ -4,6 +4,7 @@ import (
"errors"
"os"
"runtime"
+ "sync"
"syscall"
"unsafe"
)
@@ -15,6 +16,8 @@ import (
// Note, go1.10 is the earliest version of the Go toolchain that can
// support this abstraction.
type Launcher struct {
+ mu sync.RWMutex
+
// Note, path and args must be set, or callbackFn. They cannot
// both be empty. In such cases .Launch() will error out.
path string
@@ -108,11 +111,21 @@ func FuncLauncher(fn func(interface{}) error) *Launcher {
// 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) {
+ if attr == nil {
+ return
+ }
+ attr.mu.Lock()
+ defer attr.mu.Unlock()
attr.callbackFn = fn
}
// SetUID specifies the UID to be used by the launched command.
func (attr *Launcher) SetUID(uid int) {
+ if attr == nil {
+ return
+ }
+ attr.mu.Lock()
+ defer attr.mu.Unlock()
attr.changeUIDs = true
attr.uid = uid
}
@@ -120,6 +133,11 @@ func (attr *Launcher) SetUID(uid int) {
// SetGroups specifies the GID and supplementary groups for the
// launched command.
func (attr *Launcher) SetGroups(gid int, groups []int) {
+ if attr == nil {
+ return
+ }
+ attr.mu.Lock()
+ defer attr.mu.Unlock()
attr.changeGIDs = true
attr.gid = gid
attr.groups = groups
@@ -127,25 +145,46 @@ func (attr *Launcher) SetGroups(gid int, groups []int) {
// SetMode specifies the libcap Mode to be used by the launched command.
func (attr *Launcher) SetMode(mode Mode) {
+ if attr == nil {
+ return
+ }
+ attr.mu.Lock()
+ defer attr.mu.Unlock()
attr.changeMode = true
attr.mode = mode
}
-// SetIAB specifies the AIB capability vectors to be inherited by the
+// SetIAB specifies the IAB capability vectors to be inherited by the
// launched command. A nil value means the prevailing vectors of the
-// parent will be inherited.
+// parent will be inherited. Note, a duplicate of the provided IAB
+// tuple is actually stored, so concurrent modification of the iab
+// value does not affect the launcher.
func (attr *Launcher) SetIAB(iab *IAB) {
- attr.iab = iab
+ if attr == nil {
+ return
+ }
+ attr.mu.Lock()
+ defer attr.mu.Unlock()
+ attr.iab, _ = iab.Dup()
}
// 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) {
+ if attr == nil {
+ return
+ }
+ attr.mu.Lock()
+ defer attr.mu.Unlock()
attr.chroot = root
}
// lResult is used to get the result from the doomed launcher thread.
type lResult struct {
+ // tgid holds the thread group id, which is an alias for the
+ // shared process id of the parent program.
+ tgid int
+
// tid holds the tid of the locked launching thread which dies
// as the launch completes.
tid int
@@ -197,13 +236,15 @@ func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<-
defer close(quit)
}
- pid := syscall.Getpid()
+ // Thread group ID is the process ID.
+ tgid := 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 {
+ if tid == tgid {
// 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
@@ -222,6 +263,12 @@ func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<-
return
}
+ // Provide a way to serialize the caller on the thread
+ // completing. This should be done by the one locked tid that
+ // does the ForkExec(). All the other threads have a different
+ // security context.
+ defer close(result)
+
// By never releasing the LockOSThread here, we guarantee that
// the runtime will terminate the current OS thread once this
// function returns.
@@ -231,10 +278,6 @@ func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<-
// 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
@@ -254,12 +297,12 @@ func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<-
}
}
+ var pid int
if attr.callbackFn != nil {
if err = attr.callbackFn(pa, data); err != nil {
goto abort
}
if attr.path == "" {
- pid = 0
goto abort
}
}
@@ -283,6 +326,8 @@ func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<-
}
}
if attr.iab != nil {
+ // Note, since .iab is a private copy we don't need to
+ // lock it around this call.
if err = singlesc.iabSetProc(attr.iab); err != nil {
goto abort
}
@@ -304,16 +349,30 @@ abort:
pid = -1
}
result <- lResult{
- tid: tid,
- pid: pid,
- err: err,
+ tgid: tgid,
+ tid: tid,
+ pid: pid,
+ err: err,
}
}
+// pollForThreadExit waits for a thread to terminate. Only after the
+// thread has safely exited is it safe to resume POSIX semantics
+// security state mirroring for the rest of the process threads.
+func (v lResult) pollForThreadExit() {
+ if v.tid == -1 {
+ return
+ }
+ for syscall.Tgkill(v.tgid, v.tid, 0) == nil {
+ runtime.Gosched()
+ }
+ scwSetState(launchActive, launchIdle, v.tid)
+}
+
// 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.
+// return details from the callback function's 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
@@ -325,15 +384,15 @@ abort:
// callback return value.
//
// Note, while the disposable security state thread makes some
-// oprerations seem more isolated - they are *not securely
+// operations 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
+// 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
@@ -343,24 +402,22 @@ func (attr *Launcher) Launch(data interface{}) (int, error) {
if !LaunchSupported {
return -1, ErrNoLaunch
}
+ if attr == nil {
+ return -1, ErrLaunchFailed
+ }
+ attr.mu.RLock()
+ defer attr.mu.RUnlock()
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()
- }
+ v, ok := <-result
+ if !ok {
+ return -1, ErrLaunchFailed
}
+ <-result // blocks until the launch() goroutine exits
+ v.pollForThreadExit()
+ return v.pid, v.err
}