utils_windows.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. //go:build windows
  2. package csplugin
  3. import (
  4. "fmt"
  5. "os"
  6. "os/exec"
  7. "os/user"
  8. "path/filepath"
  9. "reflect"
  10. "strings"
  11. "syscall"
  12. "unsafe"
  13. "github.com/pkg/errors"
  14. log "github.com/sirupsen/logrus"
  15. "golang.org/x/sys/windows"
  16. )
  17. var (
  18. advapi32 = syscall.NewLazyDLL("advapi32.dll")
  19. procGetAce = advapi32.NewProc("GetAce")
  20. )
  21. type AclSizeInformation struct {
  22. AceCount uint32
  23. AclBytesInUse uint32
  24. AclBytesFree uint32
  25. }
  26. type Acl struct {
  27. AclRevision uint8
  28. Sbz1 uint8
  29. AclSize uint16
  30. AceCount uint16
  31. Sbz2 uint16
  32. }
  33. type AccessAllowedAce struct {
  34. AceType uint8
  35. AceFlags uint8
  36. AceSize uint16
  37. AccessMask uint32
  38. SidStart uint32
  39. }
  40. const ACCESS_ALLOWED_ACE_TYPE = 0
  41. const ACCESS_DENIED_ACE_TYPE = 1
  42. func CheckPerms(path string) error {
  43. log.Debugf("checking permissions of %s\n", path)
  44. systemSid, err := windows.CreateWellKnownSid(windows.WELL_KNOWN_SID_TYPE(windows.WinLocalSystemSid))
  45. if err != nil {
  46. return errors.Wrap(err, "while creating SYSTEM well known sid")
  47. }
  48. adminSid, err := windows.CreateWellKnownSid(windows.WELL_KNOWN_SID_TYPE(windows.WinBuiltinAdministratorsSid))
  49. if err != nil {
  50. return errors.Wrap(err, "while creating built-in Administrators well known sid")
  51. }
  52. currentUser, err := user.Current()
  53. if err != nil {
  54. return errors.Wrap(err, "while getting current user")
  55. }
  56. currentUserSid, _, _, err := windows.LookupSID("", currentUser.Username)
  57. if err != nil {
  58. return errors.Wrap(err, "while looking up current user sid")
  59. }
  60. sd, err := windows.GetNamedSecurityInfo(path, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION)
  61. if err != nil {
  62. return errors.Wrap(err, "while getting owner security info")
  63. }
  64. if !sd.IsValid() {
  65. return errors.New("security descriptor is invalid")
  66. }
  67. owner, _, err := sd.Owner()
  68. if err != nil {
  69. return errors.Wrap(err, "while getting owner")
  70. }
  71. if !owner.IsValid() {
  72. return errors.New("owner is invalid")
  73. }
  74. if !owner.Equals(systemSid) && !owner.Equals(currentUserSid) && !owner.Equals(adminSid) {
  75. return fmt.Errorf("plugin at %s is not owned by SYSTEM, Administrators or by current user, but by %s", path, owner.String())
  76. }
  77. dacl, _, err := sd.DACL()
  78. if err != nil {
  79. return errors.Wrap(err, "while getting DACL")
  80. }
  81. if dacl == nil {
  82. return fmt.Errorf("no DACL found on plugin, meaning fully permissive access on plugin %s", path)
  83. }
  84. if err != nil {
  85. return errors.Wrap(err, "while looking up current user sid")
  86. }
  87. rs := reflect.ValueOf(dacl).Elem()
  88. /*
  89. For reference, the structure of the ACL type is:
  90. type ACL struct {
  91. aclRevision byte
  92. sbz1 byte
  93. aclSize uint16
  94. aceCount uint16
  95. sbz2 uint16
  96. }
  97. As the field are not exported, we have to use reflection to access them, this should not be an issue as the structure won't (probably) change any time soon.
  98. */
  99. aceCount := rs.Field(3).Uint()
  100. for i := uint64(0); i < aceCount; i++ {
  101. ace := &AccessAllowedAce{}
  102. ret, _, _ := procGetAce.Call(uintptr(unsafe.Pointer(dacl)), uintptr(i), uintptr(unsafe.Pointer(&ace)))
  103. if ret == 0 {
  104. return errors.Wrap(windows.GetLastError(), "while getting ACE")
  105. }
  106. log.Debugf("ACE %d: %+v\n", i, ace)
  107. if ace.AceType == ACCESS_DENIED_ACE_TYPE {
  108. continue
  109. }
  110. aceSid := (*windows.SID)(unsafe.Pointer(&ace.SidStart))
  111. if aceSid.Equals(systemSid) || aceSid.Equals(adminSid) {
  112. log.Debugf("Not checking permission for well-known SID %s", aceSid.String())
  113. continue
  114. }
  115. if aceSid.Equals(currentUserSid) {
  116. log.Debugf("Not checking permission for current user %s", currentUser.Username)
  117. continue
  118. }
  119. log.Debugf("Checking permission for SID %s", aceSid.String())
  120. denyMask := ^(windows.FILE_GENERIC_READ | windows.FILE_GENERIC_EXECUTE)
  121. if ace.AccessMask&uint32(denyMask) != 0 {
  122. return fmt.Errorf("only SYSTEM, Administrators or the user currently running crowdsec can have more than read/execute on plugin %s", path)
  123. }
  124. }
  125. return nil
  126. }
  127. func getProcessAtr() (*syscall.SysProcAttr, error) {
  128. var procToken, token windows.Token
  129. proc := windows.CurrentProcess()
  130. defer windows.CloseHandle(proc)
  131. err := windows.OpenProcessToken(proc, windows.TOKEN_DUPLICATE|windows.TOKEN_ADJUST_DEFAULT|
  132. windows.TOKEN_QUERY|windows.TOKEN_ASSIGN_PRIMARY|windows.TOKEN_ADJUST_GROUPS|windows.TOKEN_ADJUST_PRIVILEGES, &procToken)
  133. if err != nil {
  134. return nil, errors.Wrapf(err, "while opening process token")
  135. }
  136. defer procToken.Close()
  137. err = windows.DuplicateTokenEx(procToken, 0, nil, windows.SecurityImpersonation,
  138. windows.TokenPrimary, &token)
  139. if err != nil {
  140. return nil, errors.Wrapf(err, "while duplicating token")
  141. }
  142. //Remove all privileges from the token
  143. err = windows.AdjustTokenPrivileges(token, true, nil, 0, nil, nil)
  144. if err != nil {
  145. return nil, errors.Wrapf(err, "while adjusting token privileges")
  146. }
  147. //Run the plugin as a medium integrity level process
  148. //For some reasons, low level integrity don't work, the plugin and crowdsec cannot communicate over the TCP socket
  149. sid, err := windows.CreateWellKnownSid(windows.WELL_KNOWN_SID_TYPE(windows.WinMediumLabelSid))
  150. if err != nil {
  151. return nil, err
  152. }
  153. tml := &windows.Tokenmandatorylabel{}
  154. tml.Label.Attributes = windows.SE_GROUP_INTEGRITY
  155. tml.Label.Sid = sid
  156. err = windows.SetTokenInformation(token, windows.TokenIntegrityLevel,
  157. (*byte)(unsafe.Pointer(tml)), tml.Size())
  158. if err != nil {
  159. token.Close()
  160. return nil, errors.Wrapf(err, "while setting token information")
  161. }
  162. return &windows.SysProcAttr{
  163. CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
  164. Token: syscall.Token(token),
  165. }, nil
  166. }
  167. func (pb *PluginBroker) CreateCmd(binaryPath string) (*exec.Cmd, error) {
  168. var err error
  169. cmd := exec.Command(binaryPath)
  170. cmd.SysProcAttr, err = getProcessAtr()
  171. if err != nil {
  172. return nil, errors.Wrap(err, "while getting process attributes")
  173. }
  174. return cmd, err
  175. }
  176. func getPluginTypeAndSubtypeFromPath(path string) (string, string, error) {
  177. pluginFileName := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
  178. parts := strings.Split(pluginFileName, "-")
  179. if len(parts) < 2 {
  180. return "", "", fmt.Errorf("plugin name %s is invalid. Name should be like {type-name}", path)
  181. }
  182. return strings.Join(parts[:len(parts)-1], "-"), parts[len(parts)-1], nil
  183. }
  184. func pluginIsValid(path string) error {
  185. var err error
  186. // check if it exists
  187. if _, err = os.Stat(path); err != nil {
  188. return errors.Wrap(err, fmt.Sprintf("plugin at %s does not exist", path))
  189. }
  190. // check if it is owned by root
  191. err = CheckPerms(path)
  192. if err != nil {
  193. return err
  194. }
  195. return nil
  196. }