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