DRAFT nrpt/gpo

Fixes #20187

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
This commit is contained in:
Aaron Klotz
2026-07-03 09:08:11 -06:00
parent 69ee776dfb
commit b3e9386804
8 changed files with 729 additions and 31 deletions

View 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)
}
}

View File

@@ -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.

View File

@@ -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(&regext.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(&regext.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
}
}
}

View 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]]
}

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

View File

@@ -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

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

View File

@@ -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 {