github.com/crowdsecurity/crowdsec@v1.6.1/pkg/csplugin/utils_windows.go (about)

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