mirror of
https://github.com/tailscale/tailscale.git
synced 2026-07-18 19:42:11 -04:00
DRAFT nrpt/gpo
Fixes #20187 Signed-off-by: Aaron Klotz <aaron@tailscale.com>
This commit is contained in:
38
cmd/dumpgpos/dumpgpos_windows.go
Normal file
38
cmd/dumpgpos/dumpgpos_windows.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
47
util/winutil/gp/list_string_windows.go
Normal file
47
util/winutil/gp/list_string_windows.go
Normal file
@@ -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]]
|
||||
}
|
||||
149
util/winutil/gp/list_windows.go
Normal file
149
util/winutil/gp/list_windows.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
353
util/winutil/gp/regext/regext_windows.go
Normal file
353
util/winutil/gp/regext/regext_windows.go
Normal file
@@ -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}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user