github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/pipeowner_windows.go (about)

     1  // Copyright 2018 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  //go:build windows
     5  // +build windows
     6  
     7  package libkb
     8  
     9  import (
    10  	"syscall"
    11  	"unsafe"
    12  
    13  	"github.com/keybase/client/go/logger"
    14  	"golang.org/x/sys/windows"
    15  )
    16  
    17  var (
    18  	modkernel32        = windows.NewLazySystemDLL("kernel32.dll")
    19  	procWaitNamedPipeW = modkernel32.NewProc("WaitNamedPipeW")
    20  )
    21  
    22  const ERROR_PIPE_BUSY = 231
    23  
    24  type _PipeBusyError struct{}
    25  
    26  var PipeBusyError _PipeBusyError
    27  
    28  func (e _PipeBusyError) Error() string {
    29  	return "All pipe instances are busy"
    30  }
    31  
    32  func waitNamedPipe(name string, timeout uint32) (err error) {
    33  	rawName, e1 := syscall.UTF16PtrFromString(name)
    34  	if e1 != nil {
    35  		return e1
    36  	}
    37  
    38  	r1, _, e2 := procWaitNamedPipeW.Call(uintptr(unsafe.Pointer(rawName)), uintptr(timeout))
    39  	if r1 == 0 {
    40  		return e2
    41  	}
    42  	return
    43  }
    44  
    45  // currentProcessUserSid is a utility to get the
    46  // SID of the current user running the process.
    47  func currentProcessUserSid() (*windows.SID, error) {
    48  	tok, err := windows.OpenCurrentProcessToken()
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  	defer tok.Close()
    53  	tokUser, err := tok.GetTokenUser()
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	return (*windows.SID)(tokUser.User.Sid), nil
    58  }
    59  
    60  // currentProcessUserSid is a utility to get the
    61  // SID of the named pipe
    62  func GetFileUserSid(name string) (*windows.SID, error) {
    63  	var userSID *windows.SID
    64  	var secDesc windows.Handle
    65  
    66  	err := GetNamedSecurityInfo(name, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &userSID, nil, nil, nil, &secDesc)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	return userSID, nil
    71  }
    72  
    73  type AccountInfo struct {
    74  	Account string `json:"account"`
    75  	Domain  string `json:"domain"`
    76  	Type    uint32 `json:"type"`
    77  	SID     string `json:"SID"`
    78  	Err     error  `json:"error"`
    79  }
    80  
    81  type PipeOwnerInfo struct {
    82  	IsOwner     bool        `json:"isOwner"`
    83  	PipeAccount AccountInfo `json:"pipe"`
    84  	UserAccount AccountInfo `json:"user"`
    85  }
    86  
    87  func IsPipeowner(log logger.Logger, name string) (owner PipeOwnerInfo, err error) {
    88  	log.Debug("+ IsPipeowner(%s)", name)
    89  	defer func() {
    90  		log.Debug("- IsPiperowner -> (%v, %v)", owner, err)
    91  	}()
    92  	userSid, err := currentProcessUserSid()
    93  	if err != nil {
    94  		return owner, err
    95  	}
    96  
    97  	pipeSid, err := GetFileUserSid(name)
    98  	if err == PipeBusyError {
    99  		// If at least one instance of the pipe has been created, this function
   100  		// will wait timeout milliseconds for it to become available.
   101  		// It will return immediately regardless of timeout, if no instances
   102  		// of the named pipe have been created yet.
   103  		// If this returns with no error, there is a pipe available.
   104  		err2 := waitNamedPipe(name, 1000)
   105  		if err2 != nil {
   106  			return owner, err // return original busy error
   107  		}
   108  		pipeSid, err = GetFileUserSid(name)
   109  	}
   110  	if err != nil {
   111  		return owner, err
   112  	}
   113  	owner.IsOwner = windows.EqualSid(pipeSid, userSid)
   114  	owner.PipeAccount.Account, owner.PipeAccount.Domain, owner.PipeAccount.Type, owner.PipeAccount.Err = pipeSid.LookupAccount("")
   115  	owner.PipeAccount.SID = pipeSid.String()
   116  	owner.UserAccount.Account, owner.UserAccount.Domain, owner.UserAccount.Type, owner.UserAccount.Err = userSid.LookupAccount("")
   117  	owner.UserAccount.SID = userSid.String()
   118  
   119  	if !owner.IsOwner {
   120  		// If the pipe is served by an admin, let local security policies control access
   121  		// https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems
   122  		if owner.PipeAccount.SID == "S-1-5-32-544" && owner.PipeAccount.Type == syscall.SidTypeAlias {
   123  			owner.IsOwner = true
   124  		}
   125  	}
   126  	log.Debug("%v", owner)
   127  	return owner, nil
   128  }