| package cap |
| |
| import ( |
| "errors" |
| "os" |
| "runtime" |
| "syscall" |
| "unsafe" |
| ) |
| |
| // Launcher holds a configuration for launching a child process with |
| // capability state different from (generally more restricted than) |
| // the parent. |
| // |
| // Note, go1.10 is the earliest version of the Go toolchain that can |
| // support this abstraction. |
| type Launcher struct { |
| path string |
| args []string |
| env []string |
| |
| callbackFn func(pa *syscall.ProcAttr, data interface{}) error |
| |
| 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, |
| } |
| } |
| |
| // Callback specifies a callback 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. Any returned error value of said |
| // function will terminate the launch process. A nil callback (the |
| // default) is ignored. The specified callback fn should not call any |
| // "cap" package functions since this may deadlock or generate |
| // undefined behavior for the parent 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 { |
| pid int |
| 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() |
| // Wait until we are not scheduled on the parent thread. We |
| // will exit this thread once the child has launched, and |
| // don't want other goroutines to use this thread afterwards. |
| runtime.LockOSThread() |
| tid := syscall.Gettid() |
| if tid == pid { |
| // Force the go runtime to find a new thread to run on. |
| 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. |
| |
| // 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) |
| |
| pa := &syscall.ProcAttr{ |
| Files: []uintptr{0, 1, 2}, |
| } |
| var err error |
| var needChroot bool |
| |
| 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 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{pid: pid, err: err} |
| } |
| |
| // Launch performs a new program launch with security state specified |
| // in the supplied attr settings. |
| func (attr *Launcher) Launch(data interface{}) (int, error) { |
| if attr.path == "" || len(attr.args) == 0 { |
| return -1, ErrLaunchFailed |
| } |
| if !LaunchSupported { |
| return -1, ErrNoLaunch |
| } |
| |
| scwMu.Lock() |
| defer scwMu.Unlock() |
| result := make(chan lResult) |
| |
| go launch(result, attr, data, nil) |
| for { |
| select { |
| case v, ok := <-result: |
| if !ok { |
| return -1, ErrLaunchFailed |
| } |
| return v.pid, v.err |
| default: |
| runtime.Gosched() |
| } |
| } |
| } |