1package cap 2 3import ( 4 "errors" 5 "os" 6 "runtime" 7 "syscall" 8 "unsafe" 9) 10 11// Launcher holds a configuration for launching a child process with 12// capability state different from (generally more restricted than) 13// 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 path string 19 args []string 20 env []string 21 22 callbackFn func(pa *syscall.ProcAttr, data interface{}) error 23 24 changeUIDs bool 25 uid int 26 27 changeGIDs bool 28 gid int 29 groups []int 30 31 changeMode bool 32 mode Mode 33 34 iab *IAB 35 36 chroot string 37} 38 39// NewLauncher returns a new launcher for the specified program path 40// and args with the specified environment. 41func NewLauncher(path string, args []string, env []string) *Launcher { 42 return &Launcher{ 43 path: path, 44 args: args, 45 env: env, 46 } 47} 48 49// Callback specifies a callback for Launch() to call before changing 50// privilege. The only thing that is assumed is that the OS thread in 51// use to call this callback function at launch time will be the one 52// that ultimately calls fork. Any returned error value of said 53// function will terminate the launch process. A nil callback (the 54// default) is ignored. The specified callback fn should not call any 55// "cap" package functions since this may deadlock or generate 56// undefined behavior for the parent process. 57func (attr *Launcher) Callback(fn func(*syscall.ProcAttr, interface{}) error) { 58 attr.callbackFn = fn 59} 60 61// SetUID specifies the UID to be used by the launched command. 62func (attr *Launcher) SetUID(uid int) { 63 attr.changeUIDs = true 64 attr.uid = uid 65} 66 67// SetGroups specifies the GID and supplementary groups for the 68// launched command. 69func (attr *Launcher) SetGroups(gid int, groups []int) { 70 attr.changeGIDs = true 71 attr.gid = gid 72 attr.groups = groups 73} 74 75// SetMode specifies the libcap Mode to be used by the launched command. 76func (attr *Launcher) SetMode(mode Mode) { 77 attr.changeMode = true 78 attr.mode = mode 79} 80 81// SetIAB specifies the AIB capability vectors to be inherited by the 82// launched command. A nil value means the prevailing vectors of the 83// parent will be inherited. 84func (attr *Launcher) SetIAB(iab *IAB) { 85 attr.iab = iab 86} 87 88// SetChroot specifies the chroot value to be used by the launched 89// command. An empty value means no-change from the prevailing value. 90func (attr *Launcher) SetChroot(root string) { 91 attr.chroot = root 92} 93 94// lResult is used to get the result from the doomed launcher thread. 95type lResult struct { 96 pid int 97 err error 98} 99 100// ErrLaunchFailed is returned if a launch was aborted with no more 101// specific error. 102var ErrLaunchFailed = errors.New("launch failed") 103 104// ErrNoLaunch indicates the go runtime available to this binary does 105// not reliably support launching. See cap.LaunchSupported. 106var ErrNoLaunch = errors.New("launch not supported") 107 108// ErrAmbiguousChroot indicates that the Launcher is being used in 109// addition to a callback supplied Chroot. The former should be used 110// exclusively for this. 111var ErrAmbiguousChroot = errors.New("use Launcher for chroot") 112 113// ErrAmbiguousIDs indicates that the Launcher is being used in 114// addition to a callback supplied Credentials. The former should be 115// used exclusively for this. 116var ErrAmbiguousIDs = errors.New("use Launcher for uids and gids") 117 118// ErrAmbiguousAmbient indicates that the Launcher is being used in 119// addition to a callback supplied ambient set and the former should 120// be used exclusively in a Launch call. 121var ErrAmbiguousAmbient = errors.New("use Launcher for ambient caps") 122 123// lName is the name we temporarily give to the launcher thread. Note, 124// this will likely stick around in the process tree if the Go runtime 125// is not cleaning up locked launcher OS threads. 126var lName = []byte("cap-launcher\000") 127 128// <uapi/linux/prctl.h> 129const prSetName = 15 130 131//go:uintptrescapes 132func launch(result chan<- lResult, attr *Launcher, data interface{}, quit chan<- struct{}) { 133 if quit != nil { 134 defer close(quit) 135 } 136 137 pid := syscall.Getpid() 138 // Wait until we are not scheduled on the parent thread. We 139 // will exit this thread once the child has launched, and 140 // don't want other goroutines to use this thread afterwards. 141 runtime.LockOSThread() 142 tid := syscall.Gettid() 143 if tid == pid { 144 // Force the go runtime to find a new thread to run on. 145 quit := make(chan struct{}) 146 go launch(result, attr, data, quit) 147 148 // Wait for that go routine to complete. 149 <-quit 150 runtime.UnlockOSThread() 151 return 152 } 153 154 // By never releasing the LockOSThread here, we guarantee that 155 // the runtime will terminate the current OS thread once this 156 // function returns. 157 158 // Name the launcher thread - transient, but helps to debug if 159 // the callbackFn or something else hangs up. 160 singlesc.prctlrcall(prSetName, uintptr(unsafe.Pointer(&lName[0])), 0) 161 162 // Provide a way to serialize the caller on the thread 163 // completing. 164 defer close(result) 165 166 pa := &syscall.ProcAttr{ 167 Files: []uintptr{0, 1, 2}, 168 } 169 var err error 170 var needChroot bool 171 172 if len(attr.env) != 0 { 173 pa.Env = attr.env 174 } else { 175 pa.Env = os.Environ() 176 } 177 178 if attr.callbackFn != nil { 179 if err = attr.callbackFn(pa, data); err != nil { 180 goto abort 181 } 182 } 183 184 if needChroot, err = validatePA(pa, attr.chroot); err != nil { 185 goto abort 186 } 187 if attr.changeUIDs { 188 if err = singlesc.setUID(attr.uid); err != nil { 189 goto abort 190 } 191 } 192 if attr.changeGIDs { 193 if err = singlesc.setGroups(attr.gid, attr.groups); err != nil { 194 goto abort 195 } 196 } 197 if attr.changeMode { 198 if err = singlesc.setMode(attr.mode); err != nil { 199 goto abort 200 } 201 } 202 if attr.iab != nil { 203 if err = singlesc.iabSetProc(attr.iab); err != nil { 204 goto abort 205 } 206 } 207 208 if needChroot { 209 c := GetProc() 210 if err = c.SetFlag(Effective, true, SYS_CHROOT); err != nil { 211 goto abort 212 } 213 if err = singlesc.setProc(c); err != nil { 214 goto abort 215 } 216 } 217 pid, err = syscall.ForkExec(attr.path, attr.args, pa) 218 219abort: 220 if err != nil { 221 pid = -1 222 } 223 result <- lResult{pid: pid, err: err} 224} 225 226// Launch performs a new program launch with security state specified 227// in the supplied attr settings. 228func (attr *Launcher) Launch(data interface{}) (int, error) { 229 if attr.path == "" || len(attr.args) == 0 { 230 return -1, ErrLaunchFailed 231 } 232 if !LaunchSupported { 233 return -1, ErrNoLaunch 234 } 235 236 scwMu.Lock() 237 defer scwMu.Unlock() 238 result := make(chan lResult) 239 240 go launch(result, attr, data, nil) 241 for { 242 select { 243 case v, ok := <-result: 244 if !ok { 245 return -1, ErrLaunchFailed 246 } 247 return v.pid, v.err 248 default: 249 runtime.Gosched() 250 } 251 } 252} 253