privilege.go 5.4 KB

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