Fix native collector subscription inventory and Unicode readback

This commit is contained in:
Shirofune-Security committed 2026-09-22 09:46:28 +09:00
1 parent 03039cb1c6
commit af88b87e01
16 files changed
+327 -8

No files matched your search

+52
View File
@@ -0,0 +1,52 @@
// Read-only WEC names, returned only after complete bounded native enumeration.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace Wela.WecInventory {
public static class Reader {
public const string SourceSha256="__WELA_SOURCE_SHA256__";
const uint Capacity=1024;
[DllImport("wecapi.dll",SetLastError=true)] static extern IntPtr EcOpenSubscriptionEnum(uint flags);
[DllImport("wecapi.dll",SetLastError=true)] [return:MarshalAs(UnmanagedType.Bool)] static extern bool EcEnumNextSubscription(IntPtr enumeration,uint size,IntPtr name,out uint used);
[DllImport("wecapi.dll",SetLastError=true)] [return:MarshalAs(UnmanagedType.Bool)] static extern bool EcClose(IntPtr handle);
// Public only for safe allocated-buffer ABI and boundary regression tests.
public static string DecodeName(IntPtr buffer,uint used,uint capacity) {
if(buffer==IntPtr.Zero||capacity<2||capacity>Capacity||used<2||used>capacity)throw new InvalidDataException("Invalid native subscription-name buffer.");
if(Marshal.ReadInt16(buffer,checked((int)(used-1)*2))!=0)throw new InvalidDataException("Native subscription name is not terminated.");
byte[] bytes=new byte[checked((int)(used-1)*2)];Marshal.Copy(buffer,bytes,0,bytes.Length);
string name=new UnicodeEncoding(false,false,true).GetString(bytes);
if(name.IndexOf('\0')>=0)throw new InvalidDataException("Native subscription name contains an embedded terminator.");
return name;
}
// Public so duplicate, count and aggregate bounds can be tested without Windows.
public static string[] ValidateNames(string[] names) {
if(names==null||names.Length>4096)throw new InvalidDataException("Native subscription inventory exceeds 4096 entries.");
var unique=new HashSet<string>(StringComparer.OrdinalIgnoreCase);long characters=0;
foreach(string name in names) {
if(String.IsNullOrEmpty(name)||name.Length>=Capacity||name.IndexOf('\0')>=0||!unique.Add(name))throw new InvalidDataException("Native subscription inventory contains an invalid or duplicate name.");
new UnicodeEncoding(false,false,true).GetBytes(name);
characters+=name.Length+1;if(characters>1048576)throw new InvalidDataException("Native subscription inventory exceeds its total character bound.");
}
string[] result=(string[])names.Clone();Array.Sort(result,StringComparer.Ordinal);return result;
}
public static string[] ReadNames() {
IntPtr handle=EcOpenSubscriptionEnum(0);if(handle==IntPtr.Zero)throw new Win32Exception(Marshal.GetLastWin32Error());
try {
IntPtr buffer=Marshal.AllocHGlobal(checked((int)Capacity*2));
try {
var names=new List<string>();long characters=0;
while(true) {
uint used;
if(!EcEnumNextSubscription(handle,Capacity,buffer,out used)) {int error=Marshal.GetLastWin32Error();if(error==259)return ValidateNames(names.ToArray());throw new Win32Exception(error);}
string name=DecodeName(buffer,used,Capacity);characters+=name.Length+1;
if(names.Count>=4096||characters>1048576)throw new InvalidDataException("Native subscription inventory exceeded its bounds; no absence is established.");
names.Add(name);
}
}finally {Marshal.FreeHGlobal(buffer);}
}finally {EcClose(handle);}
}
}
}
+22 -1
View File
@@ -194,6 +194,27 @@ function Import-WelaWefConfig {
[pscustomobject]@{ Config=$config; Path=$full; Subscriptions=$subscriptions }
}
function Initialize-WelaWecSubscriptionInventory {
$path=Join-Path $PSScriptRoot 'WecSubscriptionInventory.cs'
$bytes=[IO.File]::ReadAllBytes($path);if($bytes.Length -gt 65536){throw 'Native inventory source exceeds its bound.'}
$sha=[Security.Cryptography.SHA256]::Create();try{$hash=([BitConverter]::ToString($sha.ComputeHash($bytes))).Replace('-','').ToLowerInvariant()}finally{$sha.Dispose()}
if(-not ('Wela.WecInventory.Reader' -as [type])){
$source=[Text.UTF8Encoding]::new($false,$true).GetString($bytes).TrimStart([char]0xfeff)
if([regex]::Matches($source,'__WELA_SOURCE_SHA256__').Count -ne 1){throw 'Native inventory source binding marker is missing or ambiguous.'}
$compile=@{TypeDefinition=$source.Replace('__WELA_SOURCE_SHA256__',$hash);ErrorAction='Stop'}
if($PSVersionTable.PSEdition -eq 'Desktop'){$compile.ReferencedAssemblies=@('System.dll','System.Core.dll')}
Add-Type @compile
}
if([Wela.WecInventory.Reader]::SourceSha256 -cne $hash){throw 'Loaded native inventory differs from its source; start a fresh process.'}
}
function Get-WelaWecSubscriptionIds {
if([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT -or -not [Environment]::Is64BitProcess){throw 'Native WEC inventory requires 64-bit Windows.'}
Initialize-WelaWecSubscriptionInventory
# The native method returns nothing until enumeration has completed successfully.
[Wela.WecInventory.Reader]::ReadNames()
}
function Read-WelaWecSubscriptionXml {
param([Parameter(Mandatory)][string]$Id)
if([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT -or -not [Environment]::Is64BitProcess){throw 'Native WEC XML reads require 64-bit Windows.'}
@@ -207,4 +228,4 @@ function Read-WelaWecSubscriptionXml {
[Wela.WecXml.Reader]::ReadXml($Id)
}
Export-ModuleMember -Function ConvertTo-WelaWefFirewallAddressKey, Test-WelaWefFirewallAddressSet, Read-WelaWecSubscriptionXml, Read-WelaWefXml, Get-WelaWefXmlKey, ConvertFrom-WelaWefQuery, Get-WelaWefAuthorization, ConvertFrom-WelaWefSubscription, Import-WelaWefConfig
Export-ModuleMember -Function Get-WelaWecSubscriptionIds, ConvertTo-WelaWefFirewallAddressKey, Test-WelaWefFirewallAddressSet, Read-WelaWecSubscriptionXml, Read-WelaWefXml, Get-WelaWefXmlKey, ConvertFrom-WelaWefQuery, Get-WelaWefAuthorization, ConvertFrom-WelaWefSubscription, Import-WelaWefConfig