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