privilege.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. //go:build windows
  2. // +build windows
  3. package winio
  4. import (
  5. "bytes"
  6. "encoding/binary"
  7. "fmt"
  8. "runtime"
  9. "sync"
  10. "syscall"
  11. "unicode/utf16"
  12. "golang.org/x/sys/windows"
  13. )
  14. //sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges
  15. //sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf
  16. //sys revertToSelf() (err error) = advapi32.RevertToSelf
  17. //sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken
  18. //sys getCurrentThread() (h syscall.Handle) = GetCurrentThread
  19. //sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW
  20. //sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW
  21. //sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW
  22. const (
  23. //revive:disable-next-line:var-naming ALL_CAPS
  24. SE_PRIVILEGE_ENABLED = windows.SE_PRIVILEGE_ENABLED
  25. //revive:disable-next-line:var-naming ALL_CAPS
  26. ERROR_NOT_ALL_ASSIGNED syscall.Errno = windows.ERROR_NOT_ALL_ASSIGNED
  27. SeBackupPrivilege = "SeBackupPrivilege"
  28. SeRestorePrivilege = "SeRestorePrivilege"
  29. SeSecurityPrivilege = "SeSecurityPrivilege"
  30. )
  31. var (
  32. privNames = make(map[string]uint64)
  33. privNameMutex sync.Mutex
  34. )
  35. // PrivilegeError represents an error enabling privileges.
  36. type PrivilegeError struct {
  37. privileges []uint64
  38. }
  39. func (e *PrivilegeError) Error() string {
  40. s := "Could not enable privilege "
  41. if len(e.privileges) > 1 {
  42. s = "Could not enable privileges "
  43. }
  44. for i, p := range e.privileges {
  45. if i != 0 {
  46. s += ", "
  47. }
  48. s += `"`
  49. s += getPrivilegeName(p)
  50. s += `"`
  51. }
  52. return s
  53. }
  54. // RunWithPrivilege enables a single privilege for a function call.
  55. func RunWithPrivilege(name string, fn func() error) error {
  56. return RunWithPrivileges([]string{name}, fn)
  57. }
  58. // RunWithPrivileges enables privileges for a function call.
  59. func RunWithPrivileges(names []string, fn func() error) error {
  60. privileges, err := mapPrivileges(names)
  61. if err != nil {
  62. return err
  63. }
  64. runtime.LockOSThread()
  65. defer runtime.UnlockOSThread()
  66. token, err := newThreadToken()
  67. if err != nil {
  68. return err
  69. }
  70. defer releaseThreadToken(token)
  71. err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED)
  72. if err != nil {
  73. return err
  74. }
  75. return fn()
  76. }
  77. func mapPrivileges(names []string) ([]uint64, error) {
  78. privileges := make([]uint64, 0, len(names))
  79. privNameMutex.Lock()
  80. defer privNameMutex.Unlock()
  81. for _, name := range names {
  82. p, ok := privNames[name]
  83. if !ok {
  84. err := lookupPrivilegeValue("", name, &p)
  85. if err != nil {
  86. return nil, err
  87. }
  88. privNames[name] = p
  89. }
  90. privileges = append(privileges, p)
  91. }
  92. return privileges, nil
  93. }
  94. // EnableProcessPrivileges enables privileges globally for the process.
  95. func EnableProcessPrivileges(names []string) error {
  96. return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED)
  97. }
  98. // DisableProcessPrivileges disables privileges globally for the process.
  99. func DisableProcessPrivileges(names []string) error {
  100. return enableDisableProcessPrivilege(names, 0)
  101. }
  102. func enableDisableProcessPrivilege(names []string, action uint32) error {
  103. privileges, err := mapPrivileges(names)
  104. if err != nil {
  105. return err
  106. }
  107. p := windows.CurrentProcess()
  108. var token windows.Token
  109. err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
  110. if err != nil {
  111. return err
  112. }
  113. defer token.Close()
  114. return adjustPrivileges(token, privileges, action)
  115. }
  116. func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error {
  117. var b bytes.Buffer
  118. _ = binary.Write(&b, binary.LittleEndian, uint32(len(privileges)))
  119. for _, p := range privileges {
  120. _ = binary.Write(&b, binary.LittleEndian, p)
  121. _ = binary.Write(&b, binary.LittleEndian, action)
  122. }
  123. prevState := make([]byte, b.Len())
  124. reqSize := uint32(0)
  125. success, err := adjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(len(prevState)), &prevState[0], &reqSize)
  126. if !success {
  127. return err
  128. }
  129. if err == ERROR_NOT_ALL_ASSIGNED { //nolint:errorlint // err is Errno
  130. return &PrivilegeError{privileges}
  131. }
  132. return nil
  133. }
  134. func getPrivilegeName(luid uint64) string {
  135. var nameBuffer [256]uint16
  136. bufSize := uint32(len(nameBuffer))
  137. err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize)
  138. if err != nil {
  139. return fmt.Sprintf("<unknown privilege %d>", luid)
  140. }
  141. var displayNameBuffer [256]uint16
  142. displayBufSize := uint32(len(displayNameBuffer))
  143. var langID uint32
  144. err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID)
  145. if err != nil {
  146. return fmt.Sprintf("<unknown privilege %s>", string(utf16.Decode(nameBuffer[:bufSize])))
  147. }
  148. return string(utf16.Decode(displayNameBuffer[:displayBufSize]))
  149. }
  150. func newThreadToken() (windows.Token, error) {
  151. err := impersonateSelf(windows.SecurityImpersonation)
  152. if err != nil {
  153. return 0, err
  154. }
  155. var token windows.Token
  156. err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, false, &token)
  157. if err != nil {
  158. rerr := revertToSelf()
  159. if rerr != nil {
  160. panic(rerr)
  161. }
  162. return 0, err
  163. }
  164. return token, nil
  165. }
  166. func releaseThreadToken(h windows.Token) {
  167. err := revertToSelf()
  168. if err != nil {
  169. panic(err)
  170. }
  171. h.Close()
  172. }