AdGuardHome/internal/permcheck/security_windows.go
Eugene Burkov 3895cfb4f0 Pull request 2312: 7400 Windows permcheck
Updates #7400.

Squashed commit of the following:

commit f50d7c200de545dc6c8ef70b39208f522033fb90
Merge: 47040a14c 37b16bcf7
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Dec 3 18:09:23 2024 +0300

    Merge branch 'master' into 7400-chown-permcheck

commit 47040a14cd
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Tue Dec 3 14:26:43 2024 +0300

    permcheck: fix nil entries

commit e1d21c576d
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Dec 2 15:37:58 2024 +0300

    permcheck: fix nil owner

commit b1fc67c4d1
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Nov 29 18:07:15 2024 +0300

    permcheck: imp doc

commit 0b6a71326e
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Nov 29 17:16:24 2024 +0300

    permcheck: imp code

commit 7dfbeda179
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Fri Nov 29 14:28:17 2024 +0300

    permcheck: imp code

commit 3a5b6aced9
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Nov 28 19:21:03 2024 +0300

    all: imp code, docs

commit c076c93669
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Thu Nov 28 15:14:06 2024 +0300

    permcheck: imp code, docs

commit 09e4ae1ba1
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Wed Nov 27 19:19:11 2024 +0300

    all: implement windows permcheck

commit b75ed7d4d3
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Nov 25 18:01:47 2024 +0300

    all: revert permissions
2024-12-03 18:26:00 +03:00

167 lines
5 KiB
Go

//go:build windows
package permcheck
import (
"fmt"
"unsafe"
"github.com/AdguardTeam/golibs/errors"
"golang.org/x/sys/windows"
)
// objectType is the type of the object for directories in context of security
// API.
const objectType windows.SE_OBJECT_TYPE = windows.SE_FILE_OBJECT
// fileDeleteChildRight is the mask bit for the right to delete a child object.
// It seems to be missing from the [windows] package.
//
// See https://learn.microsoft.com/en-us/windows-hardware/drivers/ifs/access-mask.
const fileDeleteChildRight windows.ACCESS_MASK = 0b0100_0000
// fullControlMask is the mask for full control access rights.
const fullControlMask windows.ACCESS_MASK = windows.FILE_LIST_DIRECTORY |
windows.FILE_WRITE_DATA |
windows.FILE_APPEND_DATA |
windows.FILE_READ_EA |
windows.FILE_WRITE_EA |
windows.FILE_TRAVERSE |
fileDeleteChildRight |
windows.FILE_READ_ATTRIBUTES |
windows.FILE_WRITE_ATTRIBUTES |
windows.DELETE |
windows.READ_CONTROL |
windows.WRITE_DAC |
windows.WRITE_OWNER |
windows.SYNCHRONIZE
// aceFunc is a function that handles access control entries in the
// discretionary access control list. It should return true to continue
// iterating over the entries, or false to stop.
type aceFunc = func(
hdr windows.ACE_HEADER,
mask windows.ACCESS_MASK,
sid *windows.SID,
) (cont bool)
// rangeACEs ranges over the access control entries in the discretionary access
// control list of the specified security descriptor and calls f for each one.
func rangeACEs(dacl *windows.ACL, f aceFunc) (err error) {
var errs []error
for i := range uint32(dacl.AceCount) {
var ace *windows.ACCESS_ALLOWED_ACE
err = windows.GetAce(dacl, i, &ace)
if err != nil {
errs = append(errs, fmt.Errorf("getting entry at index %d: %w", i, err))
continue
}
sid := (*windows.SID)(unsafe.Pointer(&ace.SidStart))
if !f(ace.Header, ace.Mask, sid) {
break
}
}
if err = errors.Join(errs...); err != nil {
return fmt.Errorf("checking access control entries: %w", err)
}
return nil
}
// setSecurityInfo sets the security information on the specified file, using
// ents to create a discretionary access control list. Either owner or ents can
// be nil, in which case the corresponding information is not set, but at least
// one of them should be specified.
func setSecurityInfo(fname string, owner *windows.SID, ents []windows.EXPLICIT_ACCESS) (err error) {
var secInfo windows.SECURITY_INFORMATION
var acl *windows.ACL
if len(ents) > 0 {
// TODO(e.burkov): Investigate if this whole set is necessary.
secInfo |= windows.DACL_SECURITY_INFORMATION |
windows.PROTECTED_DACL_SECURITY_INFORMATION |
windows.UNPROTECTED_DACL_SECURITY_INFORMATION
acl, err = windows.ACLFromEntries(ents, nil)
if err != nil {
return fmt.Errorf("creating access control list: %w", err)
}
}
if owner != nil {
secInfo |= windows.OWNER_SECURITY_INFORMATION
}
if secInfo == 0 {
return errors.Error("no security information to set")
}
err = windows.SetNamedSecurityInfo(fname, objectType, secInfo, owner, nil, acl, nil)
if err != nil {
return fmt.Errorf("setting security info: %w", err)
}
return nil
}
// getSecurityInfo retrieves the security information for the specified file.
func getSecurityInfo(fname string) (dacl *windows.ACL, owner *windows.SID, err error) {
// desiredSecInfo defines the parts of a security descriptor to retrieve.
const desiredSecInfo windows.SECURITY_INFORMATION = windows.OWNER_SECURITY_INFORMATION |
windows.DACL_SECURITY_INFORMATION |
windows.PROTECTED_DACL_SECURITY_INFORMATION |
windows.UNPROTECTED_DACL_SECURITY_INFORMATION
sd, err := windows.GetNamedSecurityInfo(fname, objectType, desiredSecInfo)
if err != nil {
return nil, nil, fmt.Errorf("getting security descriptor: %w", err)
}
owner, _, err = sd.Owner()
if err != nil {
return nil, nil, fmt.Errorf("getting owner sid: %w", err)
}
dacl, _, err = sd.DACL()
if err != nil {
return nil, nil, fmt.Errorf("getting discretionary access control list: %w", err)
}
return dacl, owner, nil
}
// newFullExplicitAccess creates a new explicit access entry with full control
// permissions.
func newFullExplicitAccess(sid *windows.SID) (accEnt windows.EXPLICIT_ACCESS) {
return windows.EXPLICIT_ACCESS{
AccessPermissions: fullControlMask,
AccessMode: windows.GRANT_ACCESS,
Inheritance: windows.SUB_CONTAINERS_AND_OBJECTS_INHERIT,
Trustee: windows.TRUSTEE{
TrusteeForm: windows.TRUSTEE_IS_SID,
TrusteeType: windows.TRUSTEE_IS_UNKNOWN,
TrusteeValue: windows.TrusteeValueFromSID(sid),
},
}
}
// newDenyExplicitAccess creates a new explicit access entry with specified deny
// permissions.
func newDenyExplicitAccess(
sid *windows.SID,
mask windows.ACCESS_MASK,
) (accEnt windows.EXPLICIT_ACCESS) {
return windows.EXPLICIT_ACCESS{
AccessPermissions: mask,
AccessMode: windows.DENY_ACCESS,
Inheritance: windows.SUB_CONTAINERS_AND_OBJECTS_INHERIT,
Trustee: windows.TRUSTEE{
TrusteeForm: windows.TRUSTEE_IS_SID,
TrusteeType: windows.TRUSTEE_IS_UNKNOWN,
TrusteeValue: windows.TrusteeValueFromSID(sid),
},
}
}