
Some minor improvements, but biggest for here is ErrPipeListenerClosed is no longer an errors.New where the string matches the text of the now exported net.ErrClosed in the stdlib, but is just assigned to net.ErrClosed directly. This should allow us to get rid of the string check for "use of closed network connection" here now.. Signed-off-by: Daniel Canter <dcanter@microsoft.com>
198 lines
5.4 KiB
Go
198 lines
5.4 KiB
Go
//go:build windows
|
|
// +build windows
|
|
|
|
package winio
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"runtime"
|
|
"sync"
|
|
"syscall"
|
|
"unicode/utf16"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
//sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges
|
|
//sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf
|
|
//sys revertToSelf() (err error) = advapi32.RevertToSelf
|
|
//sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken
|
|
//sys getCurrentThread() (h syscall.Handle) = GetCurrentThread
|
|
//sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW
|
|
//sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW
|
|
//sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW
|
|
|
|
const (
|
|
//revive:disable-next-line:var-naming ALL_CAPS
|
|
SE_PRIVILEGE_ENABLED = windows.SE_PRIVILEGE_ENABLED
|
|
|
|
//revive:disable-next-line:var-naming ALL_CAPS
|
|
ERROR_NOT_ALL_ASSIGNED syscall.Errno = windows.ERROR_NOT_ALL_ASSIGNED
|
|
|
|
SeBackupPrivilege = "SeBackupPrivilege"
|
|
SeRestorePrivilege = "SeRestorePrivilege"
|
|
SeSecurityPrivilege = "SeSecurityPrivilege"
|
|
)
|
|
|
|
var (
|
|
privNames = make(map[string]uint64)
|
|
privNameMutex sync.Mutex
|
|
)
|
|
|
|
// PrivilegeError represents an error enabling privileges.
|
|
type PrivilegeError struct {
|
|
privileges []uint64
|
|
}
|
|
|
|
func (e *PrivilegeError) Error() string {
|
|
s := "Could not enable privilege "
|
|
if len(e.privileges) > 1 {
|
|
s = "Could not enable privileges "
|
|
}
|
|
for i, p := range e.privileges {
|
|
if i != 0 {
|
|
s += ", "
|
|
}
|
|
s += `"`
|
|
s += getPrivilegeName(p)
|
|
s += `"`
|
|
}
|
|
return s
|
|
}
|
|
|
|
// RunWithPrivilege enables a single privilege for a function call.
|
|
func RunWithPrivilege(name string, fn func() error) error {
|
|
return RunWithPrivileges([]string{name}, fn)
|
|
}
|
|
|
|
// RunWithPrivileges enables privileges for a function call.
|
|
func RunWithPrivileges(names []string, fn func() error) error {
|
|
privileges, err := mapPrivileges(names)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
token, err := newThreadToken()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer releaseThreadToken(token)
|
|
err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return fn()
|
|
}
|
|
|
|
func mapPrivileges(names []string) ([]uint64, error) {
|
|
privileges := make([]uint64, 0, len(names))
|
|
privNameMutex.Lock()
|
|
defer privNameMutex.Unlock()
|
|
for _, name := range names {
|
|
p, ok := privNames[name]
|
|
if !ok {
|
|
err := lookupPrivilegeValue("", name, &p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
privNames[name] = p
|
|
}
|
|
privileges = append(privileges, p)
|
|
}
|
|
return privileges, nil
|
|
}
|
|
|
|
// EnableProcessPrivileges enables privileges globally for the process.
|
|
func EnableProcessPrivileges(names []string) error {
|
|
return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED)
|
|
}
|
|
|
|
// DisableProcessPrivileges disables privileges globally for the process.
|
|
func DisableProcessPrivileges(names []string) error {
|
|
return enableDisableProcessPrivilege(names, 0)
|
|
}
|
|
|
|
func enableDisableProcessPrivilege(names []string, action uint32) error {
|
|
privileges, err := mapPrivileges(names)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
p := windows.CurrentProcess()
|
|
var token windows.Token
|
|
err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer token.Close()
|
|
return adjustPrivileges(token, privileges, action)
|
|
}
|
|
|
|
func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error {
|
|
var b bytes.Buffer
|
|
_ = binary.Write(&b, binary.LittleEndian, uint32(len(privileges)))
|
|
for _, p := range privileges {
|
|
_ = binary.Write(&b, binary.LittleEndian, p)
|
|
_ = binary.Write(&b, binary.LittleEndian, action)
|
|
}
|
|
prevState := make([]byte, b.Len())
|
|
reqSize := uint32(0)
|
|
success, err := adjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(len(prevState)), &prevState[0], &reqSize)
|
|
if !success {
|
|
return err
|
|
}
|
|
if err == ERROR_NOT_ALL_ASSIGNED { //nolint:errorlint // err is Errno
|
|
return &PrivilegeError{privileges}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getPrivilegeName(luid uint64) string {
|
|
var nameBuffer [256]uint16
|
|
bufSize := uint32(len(nameBuffer))
|
|
err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize)
|
|
if err != nil {
|
|
return fmt.Sprintf("<unknown privilege %d>", luid)
|
|
}
|
|
|
|
var displayNameBuffer [256]uint16
|
|
displayBufSize := uint32(len(displayNameBuffer))
|
|
var langID uint32
|
|
err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID)
|
|
if err != nil {
|
|
return fmt.Sprintf("<unknown privilege %s>", string(utf16.Decode(nameBuffer[:bufSize])))
|
|
}
|
|
|
|
return string(utf16.Decode(displayNameBuffer[:displayBufSize]))
|
|
}
|
|
|
|
func newThreadToken() (windows.Token, error) {
|
|
err := impersonateSelf(windows.SecurityImpersonation)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
var token windows.Token
|
|
err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, false, &token)
|
|
if err != nil {
|
|
rerr := revertToSelf()
|
|
if rerr != nil {
|
|
panic(rerr)
|
|
}
|
|
return 0, err
|
|
}
|
|
return token, nil
|
|
}
|
|
|
|
func releaseThreadToken(h windows.Token) {
|
|
err := revertToSelf()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
h.Close()
|
|
}
|