123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- //go:build windows
- package csplugin
- import (
- "fmt"
- "os"
- "os/exec"
- "os/user"
- "path/filepath"
- "reflect"
- "strings"
- "syscall"
- "unsafe"
- "github.com/pkg/errors"
- log "github.com/sirupsen/logrus"
- "golang.org/x/sys/windows"
- )
- var (
- advapi32 = syscall.NewLazyDLL("advapi32.dll")
- procGetAce = advapi32.NewProc("GetAce")
- )
- type AclSizeInformation struct {
- AceCount uint32
- AclBytesInUse uint32
- AclBytesFree uint32
- }
- type Acl struct {
- AclRevision uint8
- Sbz1 uint8
- AclSize uint16
- AceCount uint16
- Sbz2 uint16
- }
- type AccessAllowedAce struct {
- AceType uint8
- AceFlags uint8
- AceSize uint16
- AccessMask uint32
- SidStart uint32
- }
- const ACCESS_ALLOWED_ACE_TYPE = 0
- const ACCESS_DENIED_ACE_TYPE = 1
- func CheckPerms(path string) error {
- log.Debugf("checking permissions of %s\n", path)
- systemSid, err := windows.CreateWellKnownSid(windows.WELL_KNOWN_SID_TYPE(windows.WinLocalSystemSid))
- if err != nil {
- return errors.Wrap(err, "while creating SYSTEM well known sid")
- }
- adminSid, err := windows.CreateWellKnownSid(windows.WELL_KNOWN_SID_TYPE(windows.WinBuiltinAdministratorsSid))
- if err != nil {
- return errors.Wrap(err, "while creating built-in Administrators well known sid")
- }
- currentUser, err := user.Current()
- if err != nil {
- return errors.Wrap(err, "while getting current user")
- }
- currentUserSid, _, _, err := windows.LookupSID("", currentUser.Username)
- if err != nil {
- return errors.Wrap(err, "while looking up current user sid")
- }
- sd, err := windows.GetNamedSecurityInfo(path, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION)
- if err != nil {
- return errors.Wrap(err, "while getting owner security info")
- }
- if !sd.IsValid() {
- return errors.New("security descriptor is invalid")
- }
- owner, _, err := sd.Owner()
- if err != nil {
- return errors.Wrap(err, "while getting owner")
- }
- if !owner.IsValid() {
- return errors.New("owner is invalid")
- }
- if !owner.Equals(systemSid) && !owner.Equals(currentUserSid) && !owner.Equals(adminSid) {
- return fmt.Errorf("plugin at %s is not owned by SYSTEM, Administrators or by current user, but by %s", path, owner.String())
- }
- dacl, _, err := sd.DACL()
- if err != nil {
- return errors.Wrap(err, "while getting DACL")
- }
- if dacl == nil {
- return fmt.Errorf("no DACL found on plugin, meaning fully permissive access on plugin %s", path)
- }
- if err != nil {
- return errors.Wrap(err, "while looking up current user sid")
- }
- rs := reflect.ValueOf(dacl).Elem()
- /*
- For reference, the structure of the ACL type is:
- type ACL struct {
- aclRevision byte
- sbz1 byte
- aclSize uint16
- aceCount uint16
- sbz2 uint16
- }
- 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.
- */
- aceCount := rs.Field(3).Uint()
- for i := uint64(0); i < aceCount; i++ {
- ace := &AccessAllowedAce{}
- ret, _, _ := procGetAce.Call(uintptr(unsafe.Pointer(dacl)), uintptr(i), uintptr(unsafe.Pointer(&ace)))
- if ret == 0 {
- return errors.Wrap(windows.GetLastError(), "while getting ACE")
- }
- log.Debugf("ACE %d: %+v\n", i, ace)
- if ace.AceType == ACCESS_DENIED_ACE_TYPE {
- continue
- }
- aceSid := (*windows.SID)(unsafe.Pointer(&ace.SidStart))
- if aceSid.Equals(systemSid) || aceSid.Equals(adminSid) {
- log.Debugf("Not checking permission for well-known SID %s", aceSid.String())
- continue
- }
- if aceSid.Equals(currentUserSid) {
- log.Debugf("Not checking permission for current user %s", currentUser.Username)
- continue
- }
- log.Debugf("Checking permission for SID %s", aceSid.String())
- denyMask := ^(windows.FILE_GENERIC_READ | windows.FILE_GENERIC_EXECUTE)
- if ace.AccessMask&uint32(denyMask) != 0 {
- return fmt.Errorf("only SYSTEM, Administrators or the user currently running crowdsec can have more than read/execute on plugin %s", path)
- }
- }
- return nil
- }
- func getProcessAtr() (*syscall.SysProcAttr, error) {
- var procToken, token windows.Token
- proc := windows.CurrentProcess()
- defer windows.CloseHandle(proc)
- err := windows.OpenProcessToken(proc, windows.TOKEN_DUPLICATE|windows.TOKEN_ADJUST_DEFAULT|
- windows.TOKEN_QUERY|windows.TOKEN_ASSIGN_PRIMARY|windows.TOKEN_ADJUST_GROUPS|windows.TOKEN_ADJUST_PRIVILEGES, &procToken)
- if err != nil {
- return nil, errors.Wrapf(err, "while opening process token")
- }
- defer procToken.Close()
- err = windows.DuplicateTokenEx(procToken, 0, nil, windows.SecurityImpersonation,
- windows.TokenPrimary, &token)
- if err != nil {
- return nil, errors.Wrapf(err, "while duplicating token")
- }
- //Remove all privileges from the token
- err = windows.AdjustTokenPrivileges(token, true, nil, 0, nil, nil)
- if err != nil {
- return nil, errors.Wrapf(err, "while adjusting token privileges")
- }
- //Run the plugin as a medium integrity level process
- //For some reasons, low level integrity don't work, the plugin and crowdsec cannot communicate over the TCP socket
- sid, err := windows.CreateWellKnownSid(windows.WELL_KNOWN_SID_TYPE(windows.WinMediumLabelSid))
- if err != nil {
- return nil, err
- }
- tml := &windows.Tokenmandatorylabel{}
- tml.Label.Attributes = windows.SE_GROUP_INTEGRITY
- tml.Label.Sid = sid
- err = windows.SetTokenInformation(token, windows.TokenIntegrityLevel,
- (*byte)(unsafe.Pointer(tml)), tml.Size())
- if err != nil {
- token.Close()
- return nil, errors.Wrapf(err, "while setting token information")
- }
- return &windows.SysProcAttr{
- CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
- Token: syscall.Token(token),
- }, nil
- }
- func (pb *PluginBroker) CreateCmd(binaryPath string) (*exec.Cmd, error) {
- var err error
- cmd := exec.Command(binaryPath)
- cmd.SysProcAttr, err = getProcessAtr()
- if err != nil {
- return nil, errors.Wrap(err, "while getting process attributes")
- }
- return cmd, err
- }
- func getPluginTypeAndSubtypeFromPath(path string) (string, string, error) {
- pluginFileName := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
- parts := strings.Split(pluginFileName, "-")
- if len(parts) < 2 {
- return "", "", fmt.Errorf("plugin name %s is invalid. Name should be like {type-name}", path)
- }
- return strings.Join(parts[:len(parts)-1], "-"), parts[len(parts)-1], nil
- }
- func pluginIsValid(path string) error {
- var err error
- // check if it exists
- if _, err = os.Stat(path); err != nil {
- return errors.Wrap(err, fmt.Sprintf("plugin at %s does not exist", path))
- }
- // check if it is owned by root
- err = CheckPerms(path)
- if err != nil {
- return err
- }
- return nil
- }
|