Helpers/Interop.ps1
|
$signature = @'
using System; using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential)] public struct GENERIC_MAPPING { public uint GenericRead; public uint GenericWrite; public uint GenericExecute; public uint GenericAll; } public static class NativeMethods { [DllImport("advapi32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CreatePrivateObjectSecurityEx( System.IntPtr ParentDescriptor, System.IntPtr CreatorDescriptor, out System.IntPtr NewDescriptor, System.IntPtr ObjectType, [MarshalAs(UnmanagedType.Bool)] bool IsContainerObject, uint AutoInheritFlags, System.IntPtr Token, ref GENERIC_MAPPING GenericMapping); [DllImport("advapi32.dll", SetLastError = false)] public static extern void MapGenericMask( ref uint accessMask, ref GENERIC_MAPPING genericMapping); [DllImport("advapi32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool DestroyPrivateObjectSecurity(ref IntPtr ObjectDescriptor); [DllImport("advapi32.dll", SetLastError = true)] public static extern int GetSecurityDescriptorLength(System.IntPtr pSecurityDescriptor); } '@ if (-not ([System.Management.Automation.PSTypeName]'NativeMethods').Type) { Add-Type -TypeDefinition $signature -Language CSharp } function MarshalSecurityDescriptor { [CmdletBinding()] [OutputType([System.IntPtr])] param ( [Parameter(Mandatory = $true)] [System.Security.AccessControl.GenericSecurityDescriptor]$SecurityDescriptor ) $length = $SecurityDescriptor.BinaryLength $bytes = New-Object byte[] $length $SecurityDescriptor.GetBinaryForm($bytes, 0) $intPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($length) [System.Runtime.InteropServices.Marshal]::Copy($bytes, 0, $intPtr, $length) return $intPtr } function UnmarshalSecurityDescriptor { [CmdletBinding()] [OutputType([System.Security.AccessControl.CommonSecurityDescriptor])] param ( [Parameter(Mandatory = $true)] [IntPtr]$IntPtr, [Parameter(Mandatory = $true)] [bool]$IsDirectory ) if ($IntPtr -eq [System.IntPtr]::Zero) { return $null } $length = [NativeMethods]::GetSecurityDescriptorLength($IntPtr) $bytes = New-Object byte[] $length [System.Runtime.InteropServices.Marshal]::Copy($IntPtr, $bytes, 0, $length) return [System.Security.AccessControl.CommonSecurityDescriptor]::new($IsDirectory, $false, $bytes, 0) } function Get-FileGenericMapping { [CmdletBinding()] param () # Build generic mapping object. Since this module only deals with file system objects, # we can hard-code the file generic rights mapping. # https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-generic_mapping $genericMapping = New-Object GENERIC_MAPPING $genericMapping.GenericRead = [FileGenericRightsMapping]::FILE_GENERIC_READ $genericMapping.GenericWrite = [FileGenericRightsMapping]::FILE_GENERIC_WRITE $genericMapping.GenericExecute = [FileGenericRightsMapping]::FILE_GENERIC_EXECUTE $genericMapping.GenericAll = [FileGenericRightsMapping]::FILE_ALL_ACCESS return $genericMapping } function Get-MappedAccessMask { [CmdletBinding()] [OutputType([int])] param ( [Parameter(Mandatory = $true)] [int]$AccessMask ) # Believe it or not, this is the only way of converting negative ints to uint in powershell... $bytes = [BitConverter]::GetBytes($AccessMask) $accessMaskUint = [BitConverter]::ToUInt32($bytes, 0) $genericMapping = Get-FileGenericMapping [NativeMethods]::MapGenericMask([ref] $accessMaskUint, [ref] $genericMapping) # Convert back from uint to int $bytes = [BitConverter]::GetBytes($accessMaskUint) return [BitConverter]::ToInt32($bytes, 0) } function CreatePrivateObjectSecurityEx { [CmdletBinding()] [OutputType([System.Security.AccessControl.GenericSecurityDescriptor])] param ( [Parameter(Mandatory = $true)] [AllowNull()] [System.Security.AccessControl.GenericSecurityDescriptor]$ParentDescriptor, [Parameter(Mandatory = $true)] [System.Security.AccessControl.GenericSecurityDescriptor]$CreatorDescriptor, [Parameter(Mandatory = $true)] [bool]$IsDirectory ) $parentSdIntPtr = [System.IntPtr]::Zero $creatorSdIntPtr = [System.IntPtr]::Zero $newDescriptorIntPtr = [System.IntPtr]::Zero try { # Parent is allowed to be null by this implementation. if ($null -ne $ParentDescriptor) { $parentSdIntPtr = MarshalSecurityDescriptor $ParentDescriptor } # Creator is not allowed to be null by this implementation. # This simplifying assumption is made to avoid having to deal with the token, # and is good enough for our use-case of computing inheritance on existing ACLs. $creatorSdIntPtr = MarshalSecurityDescriptor $CreatorDescriptor # Since we only deal with file system objects, we will never have object GUIDs. # We can safely set the object type to null. $objectTypeIntPtr = [System.IntPtr]::Zero [bool]$isContainerObject = $IsDirectory # We don't want to run this with a token, so set AVOID_PRIVILEGE_CHECK and AVOID_OWNER_CHECK. $autoInheritFlags = ( [AutoInheritFlags]::SEF_AVOID_PRIVILEGE_CHECK -bor [AutoInheritFlags]::SEF_AVOID_OWNER_CHECK ) # TODO: this is a hack. This logic should be in the caller... # We want to opt in to ACL inheritance, so set the SEF_DACL_AUTO_INHERIT and SEF_SACL_AUTO_INHERIT flags if the DACL/SACL are not protected. if (-not ($CreatorDescriptor.ControlFlags -band [System.Security.AccessControl.ControlFlags]::DiscretionaryAclProtected)) { $autoInheritFlags = $autoInheritFlags -bor [AutoInheritFlags]::SEF_DACL_AUTO_INHERIT } if (-not ($CreatorDescriptor.ControlFlags -band [System.Security.AccessControl.ControlFlags]::SystemAclProtected)) { $autoInheritFlags = $autoInheritFlags -bor [AutoInheritFlags]::SEF_SACL_AUTO_INHERIT } # We do not allow CreatorDescriptor to be null in this implementation, so according to # MS-DTYP 2.5.3.4, the token will never be used. Hence we can safely set it to null. # This also makes things easier for us -- one less thing to marshal. # https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/0f0c6ffc-f57d-47f8-a6c8-63889e874e24 $token = [System.IntPtr]::Zero $genericMapping = Get-FileGenericMapping $success = [NativeMethods]::CreatePrivateObjectSecurityEx( $parentSdIntPtr, $creatorSdIntPtr, [ref] $newDescriptorIntPtr, $objectTypeIntPtr, $isContainerObject, [uint32]$autoInheritFlags, $token, [ref] $genericMapping ) if (-not $success) { $errorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() Write-Error "CreatePrivateObjectSecurityEx failed with result: $success, error code: $errorCode" return $null } return UnmarshalSecurityDescriptor $newDescriptorIntPtr -IsDirectory $IsDirectory } catch { Write-Error "Failure: $_" throw $_ } finally { # Free all the allocated memory if ($parentSdIntPtr -ne [System.IntPtr]::Zero) { [System.Runtime.InteropServices.Marshal]::FreeHGlobal($parentSdIntPtr) } if ($creatorSdIntPtr -ne [System.IntPtr]::Zero) { [System.Runtime.InteropServices.Marshal]::FreeHGlobal($creatorSdIntPtr) } if ($newDescriptorIntPtr -ne [System.IntPtr]::Zero) { $success = [NativeMethods]::DestroyPrivateObjectSecurity([ref] $newDescriptorIntPtr) if (-not $success) { Write-Error "DestroyPrivateObjectSecurity failed with result: $success, error code: $errorCode" -ErrorAction Stop } } } } |