| package cap |
| |
| import ( |
| "runtime" |
| "sync" |
| "syscall" |
| |
| "kernel.org/pub/linux/libs/security/libcap/psx" |
| ) |
| |
| // multisc provides syscalls overridable for testing purposes that |
| // support a single kernel security state for all OS threads. |
| // We use this version when we are cgo compiling because |
| // we need to manage the native C pthreads too. |
| var multisc = &syscaller{ |
| w3: psx.Syscall3, |
| w6: psx.Syscall6, |
| r3: syscall.RawSyscall, |
| r6: syscall.RawSyscall6, |
| } |
| |
| // singlesc provides a single threaded implementation. Users should |
| // take care to ensure the thread is locked and marked nogc. |
| var singlesc = &syscaller{ |
| w3: syscall.RawSyscall, |
| w6: syscall.RawSyscall6, |
| r3: syscall.RawSyscall, |
| r6: syscall.RawSyscall6, |
| } |
| |
| // launchState is used to track which variant of the write syscalls |
| // should execute. |
| type launchState int |
| |
| // these states are used to understand when a launch is in progress. |
| const ( |
| launchIdle launchState = iota |
| launchActive |
| launchBlocked |
| ) |
| |
| // scwMu is used to fully serialize the write system calls. Note, this |
| // would generally not be necessary, but in the case of Launch we get |
| // into a situation where the launching thread is temporarily allowed |
| // to deviate from the kernel state of the rest of the runtime and |
| // allowing other threads to perform w* syscalls will potentially |
| // interfere with the launching process. In pure Go binaries, this |
| // will lead inevitably to a panic when the AllThreadsSyscall |
| // discovers inconsistent thread state. |
| // |
| // scwMu protects scwTIDs and scwState |
| var scwMu sync.Mutex |
| |
| // scwTIDs holds the thread IDs of the threads that are executing a |
| // launch it is empty when no launches are occurring. |
| var scwTIDs = make(map[int]bool) |
| |
| // scwState captures whether a launch is in progress or not. |
| var scwState = launchIdle |
| |
| // scwCond is used to announce when scwState changes to other |
| // goroutines waiting for it to change. |
| var scwCond = sync.NewCond(&scwMu) |
| |
| // scwSetState blocks until a launch state change between states from |
| // and to occurs. We use this for more context specific syscaller |
| // use. In the case that the caller is requesting a launchActive -> |
| // launchIdle transition they are declaring that tid is no longer |
| // launching. If another thread is also launching the call will |
| // complete, but the launchState will remain launchActive. |
| func scwSetState(from, to launchState, tid int) { |
| scwMu.Lock() |
| for scwState != from { |
| if scwState == launchActive && from == launchIdle && to == launchActive { |
| break // This "transition" is also allowed. |
| } |
| scwCond.Wait() |
| } |
| if from == launchIdle && to == launchActive { |
| scwTIDs[tid] = true |
| } else if from == launchActive && to == launchIdle { |
| delete(scwTIDs, tid) |
| if len(scwTIDs) != 0 { |
| to = from // not actually idle |
| } |
| } |
| scwState = to |
| scwCond.Broadcast() |
| scwMu.Unlock() |
| } |
| |
| // scwStateSC blocks until the current syscaller is available for |
| // writes, and then marks launchBlocked. Use scwSetState to perform |
| // the reverse transition (blocked->returned state value). |
| func scwStateSC() (launchState, *syscaller) { |
| sc := multisc |
| scwMu.Lock() |
| for { |
| if scwState == launchIdle { |
| break |
| } |
| runtime.LockOSThread() |
| if scwState == launchActive && scwTIDs[syscall.Gettid()] { |
| sc = singlesc |
| // note, we don't runtime.UnlockOSThread() |
| // here because we have no reason to ever |
| // allow this thread to return to normal use - |
| // we need it dead before we can return to the |
| // launchIdle state. |
| break |
| } |
| runtime.UnlockOSThread() |
| scwCond.Wait() |
| } |
| old := scwState |
| scwState = launchBlocked |
| scwCond.Broadcast() |
| scwMu.Unlock() |
| |
| return old, sc |
| } |