crowdsec/pkg/csplugin/utils_windows.go
2022-05-17 12:14:59 +02:00

242 lines
6.4 KiB
Go

//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
}