bastion 0.54.0
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// THE WINDOWS OPEN, DECIDED WHERE A LINUX TEST CAN SEE IT.
|
||||
//
|
||||
// mcpconfig_open_windows.go opens a config path with CreateFile and asks the
|
||||
// HANDLE what it holds; every decision about the answer is here, with no
|
||||
// build tag, so the Linux test run checks the Windows rules with fake handles
|
||||
// (mcpconfig_winopen_test.go). Nothing here calls Windows.
|
||||
//
|
||||
// The open does not follow a reparse point at the last component
|
||||
// (FILE_FLAG_OPEN_REPARSE_POINT): a symbolic link, junction or other
|
||||
// reparse point at a config path is opened as itself, seen as a reparse
|
||||
// point, and refused. What is checked is the handle that will be read, so
|
||||
// there is no gap between a look and an open for a swap to fall into. A
|
||||
// directory, a device, a pipe or anything else that is not a file on disk is
|
||||
// refused the same way. A reparse point in a directory ABOVE the file is
|
||||
// followed, as for any program opening the path; the file it leads to must
|
||||
// then be owned by the account whose profile it is read for, or by SYSTEM or
|
||||
// the Administrators group (mcpOwnerAllowed), so a link into another
|
||||
// account's profile reads nothing of it.
|
||||
|
||||
// The Windows values the decision reads, from the SDK headers (winbase.h,
|
||||
// winnt.h) as golang.org/x/sys/windows carries them: FILE_TYPE_DISK,
|
||||
// FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_DEVICE, FILE_ATTRIBUTE_REPARSE_POINT.
|
||||
const (
|
||||
mcpWinFileTypeDisk = 0x0001
|
||||
mcpWinAttrDirectory = 0x0010
|
||||
mcpWinAttrDevice = 0x0040
|
||||
mcpWinAttrReparse = 0x0400
|
||||
)
|
||||
|
||||
// mcpWinAdminOwners: the owners a file in any account's profile may have
|
||||
// besides that account. SYSTEM (S-1-5-18), and the Administrators group
|
||||
// (S-1-5-32-544), which is the default owner of what an elevated
|
||||
// administrator creates.
|
||||
var mcpWinAdminOwners = map[string]bool{"S-1-5-18": true, "S-1-5-32-544": true}
|
||||
|
||||
// mcpWinHandleInfo is what the open read from the handle it holds.
|
||||
type mcpWinHandleInfo struct {
|
||||
FileType uint32 // GetFileType
|
||||
Attributes uint32 // GetFileInformationByHandle's FileAttributes
|
||||
Owner string // the owner SID from GetSecurityInfo; "" when unread
|
||||
}
|
||||
|
||||
// mcpWinRegular: the handle is a file on disk that is not a directory, a
|
||||
// device or a reparse point.
|
||||
func mcpWinRegular(i mcpWinHandleInfo) bool {
|
||||
return i.FileType == mcpWinFileTypeDisk &&
|
||||
i.Attributes&(mcpWinAttrDirectory|mcpWinAttrDevice|mcpWinAttrReparse) == 0
|
||||
}
|
||||
|
||||
// mcpWinOps are the Windows calls the open makes, as values so a test can
|
||||
// stand fakes in for them. The field names are not method names anywhere in
|
||||
// the package: the claims test resolves a call through a value by name.
|
||||
type mcpWinOps struct {
|
||||
openPath func(path string) (uintptr, error)
|
||||
handleInfo func(h uintptr) (mcpWinHandleInfo, error)
|
||||
closeHandle func(h uintptr)
|
||||
asFile func(h uintptr, path string) *os.File
|
||||
}
|
||||
|
||||
// mcpWinOpenWith opens path, checks the handle, and hands back the file and
|
||||
// its owner SID. A handle that is refused is closed before return and never
|
||||
// becomes a file.
|
||||
func mcpWinOpenWith(ops mcpWinOps, path string) (*os.File, string, error) {
|
||||
h, err := ops.openPath(path)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
info, err := ops.handleInfo(h)
|
||||
if err != nil {
|
||||
ops.closeHandle(h)
|
||||
return nil, "", &os.PathError{Op: "query", Path: path, Err: err}
|
||||
}
|
||||
if !mcpWinRegular(info) {
|
||||
ops.closeHandle(h)
|
||||
return nil, "", errMCPNotRegular
|
||||
}
|
||||
owner := info.Owner
|
||||
if owner == "" {
|
||||
owner = mcpOwnerUnknown
|
||||
}
|
||||
return ops.asFile(h, path), owner, nil
|
||||
}
|
||||
|
||||
// mcpWinProfileSID is the SID whose profile directory is dir, from the
|
||||
// ProfileList (SID to ProfileImagePath, already expanded). mcpOwnerUnknown
|
||||
// when no SID or more than one names it; only an administrator's or
|
||||
// SYSTEM's files are then read there.
|
||||
func mcpWinProfileSID(dir string, profiles map[string]string) string {
|
||||
norm := func(p string) string {
|
||||
return strings.ToLower(strings.TrimRight(strings.ReplaceAll(p, "/", `\`), `\`))
|
||||
}
|
||||
want := norm(dir)
|
||||
var hits []string
|
||||
for sid, p := range profiles {
|
||||
if strings.HasPrefix(sid, "S-1-") && !strings.Contains(sid, ".") && norm(p) == want {
|
||||
hits = append(hits, sid)
|
||||
}
|
||||
}
|
||||
sort.Strings(hits)
|
||||
if len(hits) != 1 {
|
||||
return mcpOwnerUnknown
|
||||
}
|
||||
return hits[0]
|
||||
}
|
||||
Reference in New Issue
Block a user