From b3e93868047ee3e7e6eb2c9c2cd23f43916c5376 Mon Sep 17 00:00:00 2001 From: Aaron Klotz Date: Fri, 3 Jul 2026 09:08:11 -0600 Subject: [PATCH] DRAFT nrpt/gpo Fixes #20187 Signed-off-by: Aaron Klotz --- cmd/dumpgpos/dumpgpos_windows.go | 38 +++ net/dns/nrpt_windows.go | 37 +-- util/winutil/gp/gp_windows.go | 116 ++++++++ util/winutil/gp/list_string_windows.go | 47 +++ util/winutil/gp/list_windows.go | 149 ++++++++++ util/winutil/gp/mksyscall.go | 2 + util/winutil/gp/regext/regext_windows.go | 353 +++++++++++++++++++++++ util/winutil/gp/zsyscall_windows.go | 18 ++ 8 files changed, 729 insertions(+), 31 deletions(-) create mode 100644 cmd/dumpgpos/dumpgpos_windows.go create mode 100644 util/winutil/gp/list_string_windows.go create mode 100644 util/winutil/gp/list_windows.go create mode 100644 util/winutil/gp/regext/regext_windows.go diff --git a/cmd/dumpgpos/dumpgpos_windows.go b/cmd/dumpgpos/dumpgpos_windows.go new file mode 100644 index 000000000..c4d8d382f --- /dev/null +++ b/cmd/dumpgpos/dumpgpos_windows.go @@ -0,0 +1,38 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "tailscale.com/util/winutil/gp" + "tailscale.com/util/winutil/gp/regext" +) + +func main() { + polFile := flag.String("polfile", "", "path to policy file") + flag.Parse() + if *polFile == "" { + gp.DumpAppliedRegistryGPOs(func(format string, args ...any) { fmt.Printf(format+"\n", args...) }) + return + } + + f, err := os.Open(*polFile) + if err != nil { + log.Fatalf("Error opening %q: %v", *polFile, err) + } + + pr, err := regext.NewReaderTakeOwnership(f) + if err != nil { + log.Fatalf("Error creating reader for %q: %v", *polFile, err) + } + defer pr.Close() + + for rc, err := range pr.Entries() { + if err != nil { + log.Fatalf("Error parsing entry: %v", err) + } + fmt.Printf("SubKey: %q\nValueName: %q\n", rc.SubKey, rc.ValueName) + } +} diff --git a/net/dns/nrpt_windows.go b/net/dns/nrpt_windows.go index 6d9cf71b7..37cc613e9 100644 --- a/net/dns/nrpt_windows.go +++ b/net/dns/nrpt_windows.go @@ -13,13 +13,12 @@ "golang.org/x/sys/windows/registry" "tailscale.com/types/logger" "tailscale.com/util/dnsname" - "tailscale.com/util/set" "tailscale.com/util/winutil" "tailscale.com/util/winutil/gp" ) const ( - dnsBaseGP = `SOFTWARE\Policies\Microsoft\Windows NT\DNSClient` + dnsBaseGP = `SOFTWARE\Policies\Microsoft\Windows NT\DNSClient\` nrptBaseLocal = `SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsPolicyConfig` nrptBaseGP = `SOFTWARE\Policies\Microsoft\Windows NT\DNSClient\DnsPolicyConfig` @@ -108,37 +107,13 @@ func (db *nrptRuleDatabase) detectWriteAsGP() { } }() - // Get a list of all the NRPT rules under the GP subkey. - nrptKey, err := registry.OpenKey(registry.LOCAL_MACHINE, nrptBaseGP, registry.READ) - if err != nil { - if err != registry.ErrNotExist { - db.logf("Failed to open key %q with error: %v\n", nrptBaseGP, err) - } - // If this subkey does not exist then we definitely don't need to use the GP key. - return - } - defer nrptKey.Close() - - gpSubkeyNames, err := nrptKey.ReadSubKeyNames(0) - if err != nil { - db.logf("Failed to list subkeys under %q with error: %v\n", nrptBaseGP, err) - return + // TODO(aaron): just lowercase the constant + compareAgainst := strings.ToLower(dnsBaseGP) + matcher := func(testSubKey string) bool { + return strings.HasPrefix(strings.ToLower(testSubKey), compareAgainst) } - // Add *all* rules from the GP subkey into a set. - gpSubkeyMap := make(set.Set[string], len(gpSubkeyNames)) - for _, gpSubkey := range gpSubkeyNames { - gpSubkeyMap.Add(strings.ToUpper(gpSubkey)) - } - - // Remove *our* rules from the set. - for _, ourRuleID := range db.ruleIDs { - gpSubkeyMap.Delete(strings.ToUpper(ourRuleID)) - } - - // Any leftover rules do not belong to us. When group policy is being used - // by something else, we must also use the GP path. - writeAsGP = len(gpSubkeyMap) > 0 + writeAsGP, err = gp.IsPolicyAppliedToMachineRegistryKey(matcher) } // DelAllRuleKeys removes any and all NRPT rules that are owned by Tailscale. diff --git a/util/winutil/gp/gp_windows.go b/util/winutil/gp/gp_windows.go index dd13a2701..0fa0a630e 100644 --- a/util/winutil/gp/gp_windows.go +++ b/util/winutil/gp/gp_windows.go @@ -8,9 +8,13 @@ import ( "fmt" + "os" + "path/filepath" "runtime" "golang.org/x/sys/windows" + "tailscale.com/types/logger" + "tailscale.com/util/winutil/gp/regext" ) // Scope is a user or machine policy scope. @@ -77,3 +81,115 @@ func toRefreshPolicyFlags(force bool) uint32 { } return 0 } + +func IsDomainPolicyAppliedToRegistry() (bool, error) { + itr, err := AppliedGPOsForLocalMachine(®ext.REGISTRY_EXTENSION_GUID) + if err != nil { + return false, err + } + + for gpo, err := range itr { + if err != nil { + return false, err + } + + if !gpo.IsDisabled() && !gpo.IsLocal() { + return true, nil + } + } + + return false, nil +} + +func openMachineRegistryPolicyArchive() (*os.File, error) { + programData, err := windows.KnownFolderPath(windows.FOLDERID_ProgramData, windows.KF_FLAG_DEFAULT) + if err != nil { + return nil, err + } + + // https://web.archive.org/web/20260120022445/https://sdmsoftware.com/security-related/understanding-the-registry-policy-archive-file/ + return os.Open(filepath.Join(programData, "ntuser.pol")) +} + +func IsPolicyAppliedToMachineRegistryKey(subKeyMatcher func(string) bool) (bool, error) { + if subKeyMatcher == nil { + return false, os.ErrInvalid + } + + lock := NewMachinePolicyLock() + if lock.Lock() == nil { + defer lock.Unlock() + } + + haveReg, err := IsDomainPolicyAppliedToRegistry() + if err != nil || !haveReg { + return haveReg, err + } + + archive, err := openMachineRegistryPolicyArchive() + if err != nil { + return false, err + } + + rr, err := regext.NewReaderTakeOwnership(archive) + if err != nil { + return false, err + } + defer rr.Close() + + for rc, err := range rr.Entries() { + if err != nil { + return false, err + } + if subKeyMatcher(rc.SubKey) { + return true, nil + } + } + + return false, nil +} + +func DumpAppliedRegistryGPOs(logf logger.Logf) { + if logf == nil { + return + } + + lock := NewMachinePolicyLock() + if lock.Lock() == nil { + defer lock.Unlock() + } + + itr, err := AppliedGPOsForLocalMachine(®ext.REGISTRY_EXTENSION_GUID) + if err != nil { + logf("AppliedGPOsForLocalMachine failed: %v", err) + } + + for v, err := range itr { + if err != nil { + logf("Conversion failed: %v", err) + return + } + logf("Entry:\n%#v", v) + + rr, err := regext.NewReaderFromPolicyPath(v.FileSysPath) + if err != nil { + logf("NewReaderFromPolicyPath error: %v", err) + return + } + + err = func() error { + defer rr.Close() + for cmd, err := range rr.Entries() { + if err != nil { + logf("Error during iteration: %v", err) + return err + } + logf("SubKey: %s", cmd.SubKey) + } + return nil + }() + if err != nil { + return + } + } +} diff --git a/util/winutil/gp/list_string_windows.go b/util/winutil/gp/list_string_windows.go new file mode 100644 index 000000000..784c18b88 --- /dev/null +++ b/util/winutil/gp/list_string_windows.go @@ -0,0 +1,47 @@ +// Code generated by "stringer -type=GPO_LINK,GPOOptions -output=list_string_windows.go"; DO NOT EDIT. + +package gp + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[GPLinkUnknown-0] + _ = x[GPLinkMachine-1] + _ = x[GPLinkSite-2] + _ = x[GPLinkDomain-3] + _ = x[GPLinkOrganizationalUnit-4] +} + +const _GPO_LINK_name = "GPLinkUnknownGPLinkMachineGPLinkSiteGPLinkDomainGPLinkOrganizationalUnit" + +var _GPO_LINK_index = [...]uint8{0, 13, 26, 36, 48, 72} + +func (i GPO_LINK) String() string { + idx := int(i) - 0 + if i < 0 || idx >= len(_GPO_LINK_index)-1 { + return "GPO_LINK(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _GPO_LINK_name[_GPO_LINK_index[idx]:_GPO_LINK_index[idx+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[GPO_FLAG_DISABLE-1] + _ = x[GPO_FLAG_FORCE-2] +} + +const _GPOOptions_name = "GPO_FLAG_DISABLEGPO_FLAG_FORCE" + +var _GPOOptions_index = [...]uint8{0, 16, 30} + +func (i GPOOptions) String() string { + idx := int(i) - 1 + if i < 1 || idx >= len(_GPOOptions_index)-1 { + return "GPOOptions(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _GPOOptions_name[_GPOOptions_index[idx]:_GPOOptions_index[idx+1]] +} diff --git a/util/winutil/gp/list_windows.go b/util/winutil/gp/list_windows.go new file mode 100644 index 000000000..4f2bba376 --- /dev/null +++ b/util/winutil/gp/list_windows.go @@ -0,0 +1,149 @@ +package gp + +//go:generate stringer -type=GPO_LINK,GPOOptions -output=list_string_windows.go + +import ( + "iter" + "os" + "path/filepath" + "strings" + + "golang.org/x/sys/windows" + "tailscale.com/util/mak" +) + +const _GPO_LIST_FLAG_MACHINE = 0x00000001 + +type GPO_LINK int32 + +const ( + GPLinkUnknown GPO_LINK = 0 + GPLinkMachine GPO_LINK = 1 + GPLinkSite GPO_LINK = 2 + GPLinkDomain GPO_LINK = 3 + GPLinkOrganizationalUnit GPO_LINK = 4 +) + +type _GROUP_POLICY_OBJECT struct { + Options GPOOptions + Version uint32 + DSPath *uint16 + FileSysPath *uint16 + DisplayName *uint16 + GPOName [50]uint16 + GPOLink GPO_LINK + LParam uintptr + Next *_GROUP_POLICY_OBJECT + Prev *_GROUP_POLICY_OBJECT + Extensions *uint16 + LParam2 uintptr + Link *uint16 +} + +type GPOOptions uint32 + +const ( + GPO_FLAG_DISABLE GPOOptions = 0x00000001 + GPO_FLAG_FORCE GPOOptions = 0x00000002 +) + +type GPOInfo struct { + Options GPOOptions + DSPath string + FileSysPath string + DisplayName string + Name string + GPOLink GPO_LINK + Extensions map[windows.GUID][]windows.GUID + Link string +} + +func (gpoi *GPOInfo) IsDisabled() bool { + return gpoi.Options&GPO_FLAG_DISABLE != 0 +} + +func (gpoi *GPOInfo) IsForced() bool { + return gpoi.Options&GPO_FLAG_FORCE != 0 +} + +func (gpoi *GPOInfo) IsLocal() bool { + return gpoi.Link == "Local" +} + +func (gpoi *GPOInfo) IsMachine() bool { + return strings.EqualFold(filepath.Base(gpoi.FileSysPath), "Machine") +} + +// Note that the iterator must be consumed to avoid leakage +func AppliedGPOsForLocalMachine(extensionID *windows.GUID) (iter.Seq2[GPOInfo, error], error) { + return appliedGPOs(true, nil, extensionID) +} + +// Note that the iterator must be consumed to avoid leakage +func AppliedGPOsForUser(userSID *windows.SID, extensionID *windows.GUID) (iter.Seq2[GPOInfo, error], error) { + return appliedGPOs(false, userSID, extensionID) +} + +// We cannot use userSID as an indicator for machine as a nil userSID signifies +// the current user. +func appliedGPOs(machine bool, userSID *windows.SID, extensionID *windows.GUID) (iter.Seq2[GPOInfo, error], error) { + var flags uint32 + if machine { + flags |= _GPO_LIST_FLAG_MACHINE + } + + var gpos *_GROUP_POLICY_OBJECT + err := getAppliedGPOList(flags, nil, userSID, extensionID, &gpos) + if err != nil { + return nil, err + } + + return func(yield func(GPOInfo, error) bool) { + defer freeGPOList(gpos) + for cgp := gpos; cgp != nil; cgp = cgp.Next { + gpoInfo, err := makeGPOInfo(cgp) + if !yield(gpoInfo, err) || err != nil { + return + } + } + }, nil +} + +const guidStrLen = 38 + +func makeGPOInfo(gpo *_GROUP_POLICY_OBJECT) (result GPOInfo, err error) { + var zero GPOInfo + + result.Options = gpo.Options + result.DSPath = windows.UTF16PtrToString(gpo.DSPath) + result.FileSysPath = windows.UTF16PtrToString(gpo.FileSysPath) + result.DisplayName = windows.UTF16PtrToString(gpo.DisplayName) + result.Name = windows.UTF16ToString(gpo.GPOName[:]) + result.GPOLink = gpo.GPOLink + result.Link = windows.UTF16PtrToString(gpo.Link) + + strExtensions := windows.UTF16PtrToString(gpo.Extensions) + strExtensions = strings.TrimPrefix(strExtensions, "[") + strExtensions = strings.TrimSuffix(strExtensions, "]") + for extLine := range strings.SplitSeq(strExtensions, "][") { + if len(extLine) % guidStrLen != 0 { + return zero, os.ErrInvalid + } + + var guids []windows.GUID + for len(extLine) > 0 { + strGuid := extLine[:guidStrLen] + guid, err := windows.GUIDFromString(strGuid) + if err != nil { + return zero, err + } + + guids = append(guids, guid) + extLine = extLine[guidStrLen:] + } + + mak.Set(&result.Extensions, guids[0], guids[1:]) + } + + return result, nil +} diff --git a/util/winutil/gp/mksyscall.go b/util/winutil/gp/mksyscall.go index 22fd0c137..aae031feb 100644 --- a/util/winutil/gp/mksyscall.go +++ b/util/winutil/gp/mksyscall.go @@ -6,6 +6,8 @@ //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go mksyscall.go //sys enterCriticalPolicySection(machine bool) (handle policyLockHandle, err error) [int32(failretval)==0] = userenv.EnterCriticalPolicySection +//sys freeGPOList(gpoList *_GROUP_POLICY_OBJECT) (err error) [int32(failretval)==0] = userenv.FreeGPOListW +//sys getAppliedGPOList(flags uint32, machineName *uint16, userSid *windows.SID, extensionID *windows.GUID, gpoList **_GROUP_POLICY_OBJECT) (ret error) = userenv.GetAppliedGPOListW //sys impersonateLoggedOnUser(token windows.Token) (err error) [int32(failretval)==0] = advapi32.ImpersonateLoggedOnUser //sys leaveCriticalPolicySection(handle policyLockHandle) (err error) [int32(failretval)==0] = userenv.LeaveCriticalPolicySection //sys registerGPNotification(event windows.Handle, machine bool) (err error) [int32(failretval)==0] = userenv.RegisterGPNotification diff --git a/util/winutil/gp/regext/regext_windows.go b/util/winutil/gp/regext/regext_windows.go new file mode 100644 index 000000000..df967db91 --- /dev/null +++ b/util/winutil/gp/regext/regext_windows.go @@ -0,0 +1,353 @@ +package regext + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "iter" + "os" + "path/filepath" + "unsafe" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +var REGISTRY_EXTENSION_GUID = windows.GUID{0x35378EAC, 0x683F, 0x11D2, [8]byte{0xA8, 0x9A, 0x00, 0xC0, 0x4F, 0xBB, 0xCF, 0xA2}} + +var ( + ErrInvalidSignature = errors.New("invalid signature") + ErrStringTruncated = errors.New("string truncated due to excessive length") + ErrUnknownVersion = errors.New("unknown version") +) + +const ( + maxKeyLength = 255 + maxValueNameLength = 16383 + maxValueLength = 2048 // recommended, not a hard limit +) + +type UnknownValueTypeError struct { + ValueType uint32 +} + +func (e *UnknownValueTypeError) Error() string { + return fmt.Sprintf("Unknown registry value type 0x%08X", e.ValueType) +} + +type UnexpectedDataLengthError struct { + WantLen int + GotLen int +} + +func (e *UnexpectedDataLengthError) Error() string { + return fmt.Sprintf("Unexpected data length error: got %d, want %d", e.GotLen, e.WantLen) +} + +type InvalidDataLengthForTypeError struct { + ValueType uint32 + GotLen uint32 +} + +func (e *InvalidDataLengthForTypeError) Error() string { + return fmt.Sprintf("Invalid data length for type %d: %d", e.ValueType, e.GotLen) +} + +type UnexpectedCodeUnitError struct { + WantCodeUnit uint16 + GotCodeUnit uint16 +} + +func (e *UnexpectedCodeUnitError) Error() string { + return fmt.Sprintf("Unexpected UTF-16LE code unit: got 0x%04X, want 0x%04X", e.GotCodeUnit, e.WantCodeUnit) +} + +type StringReadError struct { + Inner error +} + +func (e *StringReadError) Error() string { + return fmt.Sprintf("Reading nul-terminated UTF-16LE: %v", e.Inner) +} + +func (e *StringReadError) Unwrap() error { + return e.Inner +} + +const ( + _REGFILE_SIGNATURE = 0x67655250 + _REGISTRY_FILE_VERSION = 0x00000001 + _DELETEKEYS = "**DeleteKeys" + _SECUREKEY = "**SecureKey" + _SOFT = "**soft." + _COMMENT = "**Comment:" + _DELETEVALUES = "**DeleteValues" + _DEL_VALUENAME = "**Del." + _DELVALS = "**DelVals" +) + +type RegistryCommand struct { + SubKey string + ValueName string + ValueType uint32 + Data []byte + DataTruncatedFromLength uint32 +} + +const polFileLeafName = "registry.pol" + +func NewReaderFromPolicyPath(policyFileSysPath string) (result *Reader, err error) { + polFilePath := filepath.Join(policyFileSysPath, polFileLeafName) + f, err := os.Open(polFilePath) + if err != nil { + return nil, err + } + + return NewReaderTakeOwnership(f) +} + +func NewReader(r io.ReadSeeker) (result *Reader, err error) { + if r == nil { + return nil, os.ErrInvalid + } + + rpr := &Reader{r: r} + if err := rpr.readHeader(); err != nil { + return nil, err + } + return rpr, nil +} + +func NewReaderTakeOwnership(r io.ReadSeeker) (result *Reader, err error) { + if r == nil { + return nil, os.ErrInvalid + } + + rpr := &Reader{r: r, own: true} + if err := rpr.readHeader(); err != nil { + if c, ok := r.(io.Closer); ok { + c.Close() + } + return nil, err + } + return rpr, nil +} + +func (rpr *Reader) Entries() iter.Seq2[RegistryCommand, error] { + return func(yield func(RegistryCommand, error) bool) { + for { + rc, err := rpr.nextCommand() + if err == io.EOF { + return + } + if !yield(rc, err) || err != nil { + return + } + } + } +} + +type Reader struct { + r io.ReadSeeker + own bool +} + +func (rpr *Reader) Close() error { + if !rpr.own { + return nil + } + + if c, ok := rpr.r.(io.Closer); ok { + err := c.Close() + if err == nil { + rpr.r = nil + return err + } + } + + return nil +} + +func (rpr *Reader) readHeader() error { + var sig uint32 + if err := binary.Read(rpr.r, binary.LittleEndian, &sig); err != nil { + return err + } + if sig != _REGFILE_SIGNATURE { + return ErrInvalidSignature + } + + var ver uint32 + if err := binary.Read(rpr.r, binary.LittleEndian, &ver); err != nil { + return err + } + if ver != _REGISTRY_FILE_VERSION { + return ErrUnknownVersion + } + return nil +} + +func (rpr *Reader) nextCommand() (rc RegistryCommand, err error) { + var zero RegistryCommand + if err := rpr.readCodeUnit('['); err != nil { + return zero, err + } + + rc.SubKey, err = rpr.readNulTerminated(maxKeyLength) + if err != nil { + return zero, err + } + + if err := rpr.readCodeUnit(';'); err != nil { + return zero, err + } + + rc.ValueName, err = rpr.readNulTerminated(maxValueNameLength) + if err != nil { + return zero, err + } + + if err := rpr.readCodeUnit(';'); err != nil { + return zero, err + } + + rc.ValueType, err = rpr.readValueType() + if err != nil { + return zero, err + } + + if err := rpr.readCodeUnit(';'); err != nil { + return zero, err + } + + rc.Data, rc.DataTruncatedFromLength, err = rpr.readData(rc.ValueType) + if err != nil { + return zero, err + } + + if err := rpr.readCodeUnit(']'); err != nil { + return zero, err + } + + return rc, nil +} + +// TODO ASK: Maximum size for entire file; 100MiB as of 8.1 +func (rpr *Reader) readData(valueType uint32) (result []byte, truncatedFromLength uint32, err error) { + var actualLen uint32 + if err := binary.Read(rpr.r, binary.LittleEndian, &actualLen); err != nil { + return nil, 0, err + } + + // Error out if value length does not make sense for the value type + switch valueType { + case registry.DWORD, registry.DWORD_BIG_ENDIAN: + if actualLen != uint32(unsafe.Sizeof(uint32(0))) { + return nil, 0, &InvalidDataLengthForTypeError{ + ValueType: valueType, + GotLen: actualLen, + } + } + case registry.QWORD: + if actualLen != uint32(unsafe.Sizeof(uint64(0))) { + return nil, 0, &InvalidDataLengthForTypeError{ + ValueType: valueType, + GotLen: actualLen, + } + } + case registry.SZ, registry.MULTI_SZ, registry.EXPAND_SZ, registry.LINK: + if actualLen % uint32(unsafe.Sizeof(uint16(0))) != 0 { + return nil, 0, &InvalidDataLengthForTypeError{ + ValueType: valueType, + GotLen: actualLen, + } + } + case registry.NONE: + if actualLen != 0 { + return nil, 0, &InvalidDataLengthForTypeError{ + ValueType: valueType, + GotLen: actualLen, + } + } + default: + } + + if err := rpr.readCodeUnit(';'); err != nil { + return nil, 0, err + } + + dataLen := actualLen + truncated := actualLen > maxValueLength + if truncated { + dataLen = maxValueLength + } + + result = make([]byte, dataLen) + n, err := rpr.r.Read(result) + if err != nil { + return nil, 0, err + } + if n != len(result) { + return nil, 0, &UnexpectedDataLengthError{ + WantLen: len(result), + GotLen: n, + } + } + + if truncated { + offset := int64(actualLen - dataLen) + if _, err := rpr.r.Seek(offset, io.SeekCurrent); err != nil { + return nil, actualLen, err + } + } + + return result, 0, nil +} + +func (rpr *Reader) readCodeUnit(wantCodeUnit uint16) error { + var gotCodeUnit uint16 + if err := binary.Read(rpr.r, binary.LittleEndian, &gotCodeUnit); err != nil { + return err + } + if wantCodeUnit != gotCodeUnit { + return &UnexpectedCodeUnitError{ + WantCodeUnit: wantCodeUnit, + GotCodeUnit: gotCodeUnit, + } + } + return nil +} + +func (rpr *Reader) readNulTerminated(maxLen int) (string, error) { + // maxKeyLength is a decent initial capacity for both key and value name + buf := make([]uint16, 0, maxKeyLength) + for { + var codeUnit uint16 + if err := binary.Read(rpr.r, binary.LittleEndian, &codeUnit); err != nil { + return "", &StringReadError{Inner: err} + } + if codeUnit == 0 { + break + } + buf = append(buf, codeUnit) + if len(buf) == maxLen { + return windows.UTF16ToString(buf[:]), ErrStringTruncated + } + } + + return windows.UTF16ToString(buf[:]), nil +} + +func (rpr *Reader) readValueType() (result uint32, err error) { + if err := binary.Read(rpr.r, binary.LittleEndian, &result); err != nil { + return 0, err + } + + switch result { + case registry.NONE, registry.SZ, registry.EXPAND_SZ, registry.BINARY, registry.DWORD, registry.DWORD_BIG_ENDIAN, registry.LINK, registry.MULTI_SZ, registry.QWORD: + return result, nil + default: + return 0, &UnknownValueTypeError{ValueType: result} + } +} diff --git a/util/winutil/gp/zsyscall_windows.go b/util/winutil/gp/zsyscall_windows.go index 41c240c26..9272bddf0 100644 --- a/util/winutil/gp/zsyscall_windows.go +++ b/util/winutil/gp/zsyscall_windows.go @@ -43,6 +43,8 @@ func errnoErr(e syscall.Errno) error { procImpersonateLoggedOnUser = modadvapi32.NewProc("ImpersonateLoggedOnUser") procEnterCriticalPolicySection = moduserenv.NewProc("EnterCriticalPolicySection") + procFreeGPOListW = moduserenv.NewProc("FreeGPOListW") + procGetAppliedGPOListW = moduserenv.NewProc("GetAppliedGPOListW") procLeaveCriticalPolicySection = moduserenv.NewProc("LeaveCriticalPolicySection") procRefreshPolicyEx = moduserenv.NewProc("RefreshPolicyEx") procRegisterGPNotification = moduserenv.NewProc("RegisterGPNotification") @@ -70,6 +72,22 @@ func enterCriticalPolicySection(machine bool) (handle policyLockHandle, err erro return } +func freeGPOList(gpoList *_GROUP_POLICY_OBJECT) (err error) { + r1, _, e1 := syscall.SyscallN(procFreeGPOListW.Addr(), uintptr(unsafe.Pointer(gpoList))) + if int32(r1) == 0 { + err = errnoErr(e1) + } + return +} + +func getAppliedGPOList(flags uint32, machineName *uint16, userSid *windows.SID, extensionID *windows.GUID, gpoList **_GROUP_POLICY_OBJECT) (ret error) { + r0, _, _ := syscall.SyscallN(procGetAppliedGPOListW.Addr(), uintptr(flags), uintptr(unsafe.Pointer(machineName)), uintptr(unsafe.Pointer(userSid)), uintptr(unsafe.Pointer(extensionID)), uintptr(unsafe.Pointer(gpoList))) + if r0 != 0 { + ret = syscall.Errno(r0) + } + return +} + func leaveCriticalPolicySection(handle policyLockHandle) (err error) { r1, _, e1 := syscall.SyscallN(procLeaveCriticalPolicySection.Addr(), uintptr(handle)) if int32(r1) == 0 {